From 62e50688d2d80ac764c1e7c6c411a13dec42ad6f Mon Sep 17 00:00:00 2001 From: Vic Date: Thu, 11 Jul 2024 22:57:52 -0500 Subject: [PATCH] add server --- .gitignore | 7 +- server/.eslintrc.js | 47 +++++++ server/.github/workflows/bootstrap.yml | 40 ++++++ server/.github/workflows/deploy.yml | 38 ++++++ server/.github/workflows/test.yml | 24 ++++ server/.gitignore | 8 ++ server/README.md | 1 + server/cdk.json | 41 ++++++ server/develop.sh | 0 server/jest.config.js | 8 ++ server/local/api/run.sh | 9 ++ server/local/lambdas/reserveFLights/env.json | 4 + .../local/lambdas/reserveFLights/event.json | 7 ++ .../lambdas/reserveFLights/reserveFlights.md | 16 +++ server/local/lambdas/reserveFLights/run.sh | 9 ++ server/package.json | 37 +++++- server/src/api.ts | 50 ++++++++ server/src/functions/backend/index.ts | 27 ++++ server/src/index.test.ts | 26 ++++ server/src/index.ts | 118 ++++++++++++++++++ server/src/lambda.ts | 65 ++++++++++ server/src/layers/aws-sdk/nodejs/package.json | 15 +++ server/src/layers/uuid/nodejs/package.json | 15 +++ server/src/utils/validateInputs.ts | 21 ++++ server/tsconfig.json | 32 +++++ 25 files changed, 661 insertions(+), 4 deletions(-) create mode 100644 server/.eslintrc.js create mode 100644 server/.github/workflows/bootstrap.yml create mode 100644 server/.github/workflows/deploy.yml create mode 100644 server/.github/workflows/test.yml create mode 100644 server/.gitignore create mode 100644 server/README.md create mode 100644 server/cdk.json create mode 100644 server/develop.sh create mode 100644 server/jest.config.js create mode 100644 server/local/api/run.sh create mode 100644 server/local/lambdas/reserveFLights/env.json create mode 100644 server/local/lambdas/reserveFLights/event.json create mode 100644 server/local/lambdas/reserveFLights/reserveFlights.md create mode 100644 server/local/lambdas/reserveFLights/run.sh create mode 100644 server/src/api.ts create mode 100644 server/src/functions/backend/index.ts create mode 100644 server/src/index.test.ts create mode 100644 server/src/index.ts create mode 100644 server/src/lambda.ts create mode 100644 server/src/layers/aws-sdk/nodejs/package.json create mode 100644 server/src/layers/uuid/nodejs/package.json create mode 100644 server/src/utils/validateInputs.ts create mode 100644 server/tsconfig.json diff --git a/.gitignore b/.gitignore index d7067e0..54ae354 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,9 @@ package-lock.json local.js assets/pages.js assets/partials.js -.wrangler \ No newline at end of file +.wrangler + +dist/ +.idea/ +cdk.out/ +.aws-sam/ \ No newline at end of file diff --git a/server/.eslintrc.js b/server/.eslintrc.js new file mode 100644 index 0000000..ea8ba4a --- /dev/null +++ b/server/.eslintrc.js @@ -0,0 +1,47 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended' + ], + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module' + }, + env: { + es6: true, + node: true, + browser: true + }, + // Ignore files within the node_modules directory + ignorePatterns: ['node_modules/**', 'cdk.out/**'], + rules: { + 'no-duplicate-case': 'error', + 'no-empty': 'error', + 'no-extra-semi': 'error', + 'no-func-assign': 'error', + 'no-irregular-whitespace': 'error', + 'no-unreachable': 'error', + curly: 'error', + 'dot-notation': 'error', + eqeqeq: 'error', + 'no-empty-function': 'error', + 'no-multi-spaces': 'error', + 'no-mixed-spaces-and-tabs': 'error', + 'no-trailing-spaces': 'error', + 'default-case': 'error', + 'no-fallthrough': 'error', + 'no-unused-vars': 'error', + 'no-use-before-define': 'error', + 'no-redeclare': 'error', + 'brace-style': 'error', + indent: ['error', 2], + quotes: ['error', 'single'], + semi: ['error', 'always'], + radix: 'off', + // TypeScript specific rules + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-use-before-define': 'error', + '@typescript-eslint/no-redeclare': 'error' + } +}; \ No newline at end of file diff --git a/server/.github/workflows/bootstrap.yml b/server/.github/workflows/bootstrap.yml new file mode 100644 index 0000000..6ebdae9 --- /dev/null +++ b/server/.github/workflows/bootstrap.yml @@ -0,0 +1,40 @@ +name: Manual Bootstrap CDK + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '20' + + - name: Install AWS CLI + run: | + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + sudo ./aws/install + + - name: Configure AWS CLI + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: 'us-east-1' # Replace with your preferred AWS region + run: | + aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID + aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY + aws configure set region $AWS_DEFAULT_REGION + echo "access_key: $AWS_ACCESS_KEY_ID" + echo "secret_key: AWS_SECRET_ACCESS_KEY" + + - name: Install dependencies + run: npm install + + - name: Run tests + run: npm run bootstrap diff --git a/server/.github/workflows/deploy.yml b/server/.github/workflows/deploy.yml new file mode 100644 index 0000000..42a7a96 --- /dev/null +++ b/server/.github/workflows/deploy.yml @@ -0,0 +1,38 @@ +name: Manual Deploy CDK + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '20' + + - name: Install AWS CLI + run: | + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + sudo ./aws/install + + - name: Configure AWS CLI + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: 'us-east-1' # Replace with your preferred AWS region + run: | + aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID + aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY + aws configure set region $AWS_DEFAULT_REGION + + - name: Install dependencies + run: npm install + + - name: Run tests + run: npm run deploy diff --git a/server/.github/workflows/test.yml b/server/.github/workflows/test.yml new file mode 100644 index 0000000..9b6df02 --- /dev/null +++ b/server/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Test CDK Stack + +on: + push: + branches: + - '*' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Run tests + run: npm test diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..b47e9c0 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,8 @@ +.env* +node_modules/ +dist/ +local.js +.idea/ +package-lock.json +cdk.out/ +.aws-sam/ \ No newline at end of file diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..a599827 --- /dev/null +++ b/server/README.md @@ -0,0 +1 @@ +# Serverless Backend \ No newline at end of file diff --git a/server/cdk.json b/server/cdk.json new file mode 100644 index 0000000..27f3509 --- /dev/null +++ b/server/cdk.json @@ -0,0 +1,41 @@ +{ + "app": "npx ts-node --prefer-ts-exts src/index.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "appName": "MyAwesomeApp", + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/core:stackRelativeExports": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-lambda:recognizeVersionProps": true, + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ] + } +} \ No newline at end of file diff --git a/server/develop.sh b/server/develop.sh new file mode 100644 index 0000000..e69de29 diff --git a/server/jest.config.js b/server/jest.config.js new file mode 100644 index 0000000..ea2e8d9 --- /dev/null +++ b/server/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; \ No newline at end of file diff --git a/server/local/api/run.sh b/server/local/api/run.sh new file mode 100644 index 0000000..d346315 --- /dev/null +++ b/server/local/api/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +npm run synth + +## sam.cmd for windows regular sam for linux/mac +sam.cmd build \ + -t ../../cdk.out/SagaStack-Demo.template.json + +sam.cmd local start-api diff --git a/server/local/lambdas/reserveFLights/env.json b/server/local/lambdas/reserveFLights/env.json new file mode 100644 index 0000000..1244594 --- /dev/null +++ b/server/local/lambdas/reserveFLights/env.json @@ -0,0 +1,4 @@ +{ + "Parameters": { + } +} \ No newline at end of file diff --git a/server/local/lambdas/reserveFLights/event.json b/server/local/lambdas/reserveFLights/event.json new file mode 100644 index 0000000..25b3565 --- /dev/null +++ b/server/local/lambdas/reserveFLights/event.json @@ -0,0 +1,7 @@ +{ + "departCity": "Milwaukee", + "departTime": "2024-01-01T00:00:00.000Z", + "arriveCity": "Abuja", + "arriveTime": "2024-01-03T00:00:00.000Z", + "requestId": "tripRequest123" +} \ No newline at end of file diff --git a/server/local/lambdas/reserveFLights/reserveFlights.md b/server/local/lambdas/reserveFLights/reserveFlights.md new file mode 100644 index 0000000..00a2a3b --- /dev/null +++ b/server/local/lambdas/reserveFLights/reserveFlights.md @@ -0,0 +1,16 @@ +## Local Testing of Lambda Functions +``` +sam local invoke StateMachinereserveFlightLambdaHandler77D8E840 -t ./cdk.out/CdkServerlessSagaStack.template.json +``` + +## Local Testing of API +``` +sam local start-api +``` + +[Local Step Functions Testing](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-lambda.html) + +[Local SAM Testing](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk-getting-started.html) + +Examples +-`cdk synth && sam local invoke StateMachinereserveFlightLambdaHandler77D8E840 -t ./cdk.out/CdkServerlessSagaStack.template.json --env-vars ./env.json --event ./event.json` \ No newline at end of file diff --git a/server/local/lambdas/reserveFLights/run.sh b/server/local/lambdas/reserveFLights/run.sh new file mode 100644 index 0000000..a7c66e2 --- /dev/null +++ b/server/local/lambdas/reserveFLights/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +npm run synth + +# sam.cmd for windows regular sam for linux/mac +sam.cmd local invoke StateMachinereserveFlightLambdaHandler77D8E840 \ + -t ../../../cdk.out/SagaStack-Demo.template.json \ + --env-vars ./env.json \ + --event ./event.json diff --git a/server/package.json b/server/package.json index b25c8ae..60e5acd 100644 --- a/server/package.json +++ b/server/package.json @@ -1,12 +1,43 @@ { - "name": "server", + "name": "serverless-data-machine", "version": "1.0.0", + "bin": { + "index": "src/index.js" + }, "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc", + "bootstrap": "cdk bootstrap", + "deploy": "cdk deploy --all", + "dev": "sh -c './develop.sh'", + "destroy": "cdk destroy --all", + "lint": "eslint **/*.ts", + "lint-fix": "eslint **/*.ts --fix", + "synth": "cdk synth", + "test": "jest", + "watch": "tsc -w" }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "dependencies": { + "aws-cdk-lib": "^2.147.2", + "aws-sdk": "^2.1651.0", + "constructs": "^10.3.0", + "source-map-support": "^0.5.21", + "uuid": "^10.0.0" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "aws-cdk": "^2.147.2", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.5", + "ts-node": "^10.9.2", + "typescript": "^5.5.2" + } } diff --git a/server/src/api.ts b/server/src/api.ts new file mode 100644 index 0000000..8601258 --- /dev/null +++ b/server/src/api.ts @@ -0,0 +1,50 @@ +import { Construct } from "constructs"; +import * as Apigw from 'aws-cdk-lib/aws-apigateway'; +import { IRestApi } from "aws-cdk-lib/aws-apigateway"; +import { IFunction } from 'aws-cdk-lib/aws-lambda'; + +export const createApiModels = (scope: Construct, api: IRestApi) => { + // For POST, PUT, DELETE Requests + const requestModel = new Apigw.Model(scope, 'RequestModel', { + restApi: api, + schema: { + type: Apigw.JsonSchemaType.OBJECT, + properties: { + name: { type: Apigw.JsonSchemaType.STRING }, + age: { type: Apigw.JsonSchemaType.NUMBER } + }, + required: ['name'] + } + }); + + const responseModel = new Apigw.Model(scope, 'ResponseModel', { + restApi: api, + schema: { + type: Apigw.JsonSchemaType.OBJECT, + properties: { + message: { type: Apigw.JsonSchemaType.STRING }, + status: { type: Apigw.JsonSchemaType.STRING } + } + } + }); + + return { + requestModel, + responseModel + }; +}; + +export const linkBackendApi = (scope: Construct, api: IRestApi, handler: IFunction, responseModel: any) => { + // Create a new resource (API path) + const resource = api.root.addResource('start'); + + // Add a GET method to the resource with lambda integration to the handler function + resource.addMethod('GET', new Apigw.LambdaIntegration(handler), { + methodResponses: [{ + statusCode: '200', + responseModels: { + 'application/json': responseModel + } + }] + }); +}; \ No newline at end of file diff --git a/server/src/functions/backend/index.ts b/server/src/functions/backend/index.ts new file mode 100644 index 0000000..e83c71f --- /dev/null +++ b/server/src/functions/backend/index.ts @@ -0,0 +1,27 @@ +import { v4 } from 'uuid'; + +interface ReserveRentalEvent { + rental: string; + rentalFrom: string; + rentalTo: string; + requestId: string; + runType: string; +} + +export const handler = async (event: ReserveRentalEvent) => { + const { requestId, runType, rental, rentalFrom, rentalTo } = event; + + console.log(`Reserving rentals request: ${JSON.stringify(event, null, 2)}`, process.env.TABLE_NAME); + + if (runType === 'failCarRentalReservation') { + throw new Error('Failed to book the car rental'); + } + + const carRentalReservationId = v4(); + console.log(`carReservationId: ${carRentalReservationId}`); + + return { + status: 'ok', + carRentalReservationId + }; +}; \ No newline at end of file diff --git a/server/src/index.test.ts b/server/src/index.test.ts new file mode 100644 index 0000000..67f86b9 --- /dev/null +++ b/server/src/index.test.ts @@ -0,0 +1,26 @@ +import { Template } from 'aws-cdk-lib/assertions'; +import * as CDK from 'aws-cdk-lib'; +import { ServerlessBackendStack } from './index'; + +let template: Template; + +describe('CDK Stack', () => { + beforeAll(() => { + const app = new CDK.App(); + const stack = new ServerlessBackendStack(app, 'MyTestStack'); + template = Template.fromStack(stack); + console.log('Template', JSON.stringify(template, null, 2)); + }); + + test('API Gateway Proxy Created', () => { + console.log('Testing API Gateway'); + template.hasResourceProperties('AWS::ApiGateway::Resource', { + 'PathPart': 'health', // use proxy for catch all '{proxy+}' + }); + }); + + test('1 Lambda Function Created', () => { + console.log('Testing Lambda Functions'); + template.resourceCountIs('AWS::Lambda::Function', 1); + }); +}); diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..83b56d7 --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,118 @@ +import * as CDK from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as Lambda from "aws-cdk-lib/aws-lambda"; +import * as Apigw from 'aws-cdk-lib/aws-apigateway'; +import { createLambda, createLambdaFunctions } from "./lambda"; +import { createApiModels, linkBackendApi } from "./api"; + +export class ServerlessBackendStack extends CDK.Stack { + constructor(scope: Construct, id: string, props?: CDK.StackProps) { + super(scope, id, props); + + /** + * Simple API Gateway proxy integration + */ + const api = new Apigw.RestApi(this, 'ServerlessSagaPattern', { + restApiName: 'Serverless Saga Pattern', + description: 'This service handles serverless saga pattern.', + deployOptions: { + stageName: 'test', // Create the "test" stage initially + }, + }); + console.log('New API'); + + // Add the health check route with a mock integration + const healthResource = api.root.addResource('health'); + healthResource.addMethod('GET', new Apigw.MockIntegration({ + integrationResponses: [{ + statusCode: '200', + responseTemplates: { + 'application/json': JSON.stringify({ message: 'Health check OK' }), + }, + }], + requestTemplates: { + 'application/json': '{"statusCode": 200}', + }, + }), { + methodResponses: [{ + statusCode: '200', + responseModels: { + 'application/json': Apigw.Model.EMPTY_MODEL, + }, + }], + }); + + // Layers + const awsSdkLayer = new Lambda.LayerVersion(this, 'AWSdkLayer', { + code: Lambda.Code.fromAsset('src/layers/aws-sdk'), + compatibleRuntimes: [Lambda.Runtime.NODEJS_20_X], + description: 'A layer for aws-sdk library', + }); + + const uuidLayer = new Lambda.LayerVersion(this, 'UuidLayer', { + code: Lambda.Code.fromAsset('src/layers/uuid'), + compatibleRuntimes: [Lambda.Runtime.NODEJS_20_X], + description: 'A layer for uuid library', + }); + + const layers = [ + awsSdkLayer, + uuidLayer + ]; + + const { backendServerlessLambda } = createLambdaFunctions(this, createLambda, layers) + + // Create Api Models (request, response) + const { responseModel } = createApiModels(this, api); + + // Link Saga to API + linkBackendApi(this, api, backendServerlessLambda, responseModel); + + // Deploy the API Gateway stage + const deployment = new Apigw.Deployment(this, 'Deployment', { + api, + }); + + console.log("Redeploying API with new methods"); + new Apigw.Stage(this, 'ProdStage', { + deployment, + stageName: 'prod', + }); + + // Export the API Gateway URL + new CDK.CfnOutput(this, 'ApiGatewayUrlOutput', { + value: api.url, + exportName: 'ApiGatewayUrlOutput' + }); + + // Export the API Gateway RestApiId + new CDK.CfnOutput(this, 'ApiGatewayRestApiIdOutput', { + value: api.restApiId, + exportName: 'ApiGatewayRestApiIdOutput' + }); + + // Export the API Gateway RootResourceId + new CDK.CfnOutput(this, 'ApiGatewayRootResourceIdOutput', { + value: api.root.resourceId, + exportName: 'ApiGatewayRootResourceIdOutput' + }); + } +} + +const app = new CDK.App(); +const apiStack = new ServerlessBackendStack(app, 'ServerlessBackendStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); +console.log('New ServerlessBackendStack'); \ No newline at end of file diff --git a/server/src/lambda.ts b/server/src/lambda.ts new file mode 100644 index 0000000..822cc57 --- /dev/null +++ b/server/src/lambda.ts @@ -0,0 +1,65 @@ +import { Construct } from 'constructs'; +import { Table } from 'aws-cdk-lib/aws-dynamodb'; +import { LayerVersion, Runtime } from "aws-cdk-lib/aws-lambda"; +import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { Topic } from "aws-cdk-lib/aws-sns"; +import { Fn } from "aws-cdk-lib"; +import { join } from 'path'; + +/** + * Create Lambda Function for backend + */ + +export const createLambdaFunctions = (scope: Construct, createFn: any, layers: LayerVersion[]) => { + const apiGatewayUrl = Fn.importValue('ApiGatewayUrlOutput'); + + const createFnWithLayers = (args: Record) => { + return createFn({...args, layers }); + }; + + // Lambda + const backendServerlessLambda = createFnWithLayers({ scope, id: 'backendServerlessLambdaHandler', handler: 'backend/index.ts', tables: [] }); + + return { + backendServerlessLambda + }; +}; + +/** + * Utility method to create Lambda blueprint + * @param scope + * @param id + * @param handler + * @param table + * @param layers + */ + +export const createLambda = ({ scope, id, handler, environment, tables, layers }: { + scope: Construct; + id: string; + handler: string; + environment: Record, + tables: Table[] | null; + layers?: LayerVersion[] | undefined; +}) => { + const fn = new NodejsFunction(scope, id, { + runtime: Runtime.NODEJS_20_X, + entry: join('src', 'functions', handler), + environment: { + ...(environment && { ...environment }), + TABLE_NAME: tables?.length ? tables[0]?.tableName : 'none' + }, + ...(layers && { layers }) + }); + + // Give Lambda permissions to read and write data from the DynamoDB table + if (tables) { + tables.forEach(table => { + if (table) { + table.grantReadWriteData(fn); + } + }); + } + + return fn; +}; diff --git a/server/src/layers/aws-sdk/nodejs/package.json b/server/src/layers/aws-sdk/nodejs/package.json new file mode 100644 index 0000000..a941e19 --- /dev/null +++ b/server/src/layers/aws-sdk/nodejs/package.json @@ -0,0 +1,15 @@ +{ + "name": "aws-sdk", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1653.0" + } +} diff --git a/server/src/layers/uuid/nodejs/package.json b/server/src/layers/uuid/nodejs/package.json new file mode 100644 index 0000000..39ecdd1 --- /dev/null +++ b/server/src/layers/uuid/nodejs/package.json @@ -0,0 +1,15 @@ +{ + "name": "uuid", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "uuid": "^10.0.0" + } +} diff --git a/server/src/utils/validateInputs.ts b/server/src/utils/validateInputs.ts new file mode 100644 index 0000000..567d877 --- /dev/null +++ b/server/src/utils/validateInputs.ts @@ -0,0 +1,21 @@ +import { SagaLambdaEvent } from '../functions/sagaLambda'; + +type SagaLambdaInputs = SagaLambdaEvent['queryStringParameters']; + +export const validateInputs = (inputs: SagaLambdaInputs, requestId: string, runType: string) => { + const { departCity, departTime, arriveCity, arriveTime, rental, rentalFrom, rentalTo } = inputs; + + // use zod to validate inputs here + + return { + departCity, + departTime, + arriveCity, + arriveTime, + rental, + rentalFrom, + rentalTo, + requestId, + runType + }; +}; \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..7ab4b29 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "commonjs", + "lib": [ + "es2022" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ], + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "cdk.out" + ] +} \ No newline at end of file