diff --git a/.gitignore b/.gitignore index a9aa739..e894fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,12 @@ *.d.ts node_modules +# compiled CDK entrypoint (replaced by ts-node) +index.js + +# local deployment config — never commit real account_id +config.json + # CDK asset staging directory .cdk.staging cdk.out diff --git a/cdk.json b/cdk.json index 2f0e44c..a2f2aa6 100644 --- a/cdk.json +++ b/cdk.json @@ -1,3 +1,3 @@ { - "app": "node index" + "app": "npx ts-node --prefer-ts-exts index.ts" } diff --git a/index.ts b/index.ts index e9de1c1..0bdd5ab 100644 --- a/index.ts +++ b/index.ts @@ -1,20 +1,20 @@ #!/usr/bin/env node -import {Architecture, AssetCode, Function, Runtime} from "@aws-cdk/aws-lambda"; -import {CfnApi, CfnDeployment, CfnIntegration, CfnRoute, CfnStage, CorsHttpMethod, HttpApi, HttpMethod} from "@aws-cdk/aws-apigatewayv2"; -import {HttpLambdaIntegration} from '@aws-cdk/aws-apigatewayv2-integrations'; -import {App, ConcreteDependable, Construct, Duration, RemovalPolicy, Stack, StackProps} from '@aws-cdk/core'; -import {Effect, PolicyStatement, Role, ServicePrincipal} from "@aws-cdk/aws-iam"; -import {AttributeType, BillingMode, Table} from "@aws-cdk/aws-dynamodb"; +import { Architecture, AssetCode, Function, Runtime } from "aws-cdk-lib/aws-lambda"; +import { CfnApi, CfnDeployment, CfnIntegration, CfnRoute, CfnStage, CorsHttpMethod, HttpApi, HttpMethod } from "aws-cdk-lib/aws-apigatewayv2"; +import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations"; +import { App, Duration, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { Effect, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; +import { AttributeType, BillingMode, CfnTable, Table } from "aws-cdk-lib/aws-dynamodb"; -import config from './config.json'; +import config from "./config.json"; class ChatAppStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const tableName = "abovevtt"; - // initialise api - const name = id + "-api" + const name = id + "-api"; const api = new CfnApi(this, name, { name: "AboveVTTBackend", protocolType: "WEBSOCKET", @@ -27,14 +27,18 @@ class ChatAppStack extends Stack { name: "campaignId", type: AttributeType.STRING, }, - sortKey:{ + sortKey: { name: "objectId", - type: AttributeType.STRING + type: AttributeType.STRING, }, billingMode: BillingMode.PAY_PER_REQUEST, removalPolicy: RemovalPolicy.RETAIN, }); + // Preserve the CloudFormation logical ID so CDK v1→v2 migration does not + // replace the live table. Verify the ID with `cdk diff` before first deploy. + (table.node.defaultChild as CfnTable).overrideLogicalId("abovevttbackendapitableF5D5D98F"); + table.addGlobalSecondaryIndex({ indexName: "connectionIds", partitionKey: { @@ -49,17 +53,15 @@ class ChatAppStack extends Stack { name: "campaignId", type: AttributeType.STRING, }, - sortKey:{ + sortKey: { name: "sceneId", - type: AttributeType.STRING + type: AttributeType.STRING, }, }); - const httpApi = new HttpApi(this, 'HttpApi',{ - corsPreflight:{ - allowHeaders:[ - 'Content-Type', - ], + const httpApi = new HttpApi(this, "HttpApi", { + corsPreflight: { + allowHeaders: ["Content-Type"], allowMethods: [ CorsHttpMethod.OPTIONS, CorsHttpMethod.GET, @@ -67,96 +69,85 @@ class ChatAppStack extends Stack { CorsHttpMethod.PUT, CorsHttpMethod.PATCH, CorsHttpMethod.DELETE, - ], - allowOrigins:['*'], - } + ], + allowOrigins: ["*"], + }, }); - const abovevttServicesFunc = new Function(this,"abovevtt-services-lambda",{ - code: new AssetCode('./abovevttServices'), + const abovevttServicesFunc = new Function(this, "abovevtt-services-lambda", { + code: new AssetCode("./abovevttServices"), architecture: Architecture.X86_64, - handler: 'app.handler', + handler: "app.handler", runtime: Runtime.NODEJS_12_X, timeout: Duration.seconds(30), memorySize: 256, environment: { - "TABLE_NAME": tableName, - } + TABLE_NAME: tableName, + }, }); table.grantReadWriteData(abovevttServicesFunc); - const abovevttServicesIntegration=new HttpLambdaIntegration('abovevttServices',abovevttServicesFunc,{ - }); + const abovevttServicesIntegration = new HttpLambdaIntegration("abovevttServices", abovevttServicesFunc); httpApi.addRoutes({ - path: '/services', - methods: [ HttpMethod.GET , HttpMethod.PUT, HttpMethod.POST], + path: "/services", + methods: [HttpMethod.GET, HttpMethod.PUT, HttpMethod.POST], integration: abovevttServicesIntegration, - }); - - - - + }); - const connectFunc = new Function(this, 'connect-lambda', { - code: new AssetCode('./onconnect'), - handler: 'app.handler', + const connectFunc = new Function(this, "connect-lambda", { + code: new AssetCode("./onconnect"), + handler: "app.handler", runtime: Runtime.NODEJS_12_X, timeout: Duration.seconds(30), memorySize: 256, environment: { - "TABLE_NAME": tableName, - } + TABLE_NAME: tableName, + }, }); + table.grantReadWriteData(connectFunc); - table.grantReadWriteData(connectFunc) - - const disconnectFunc = new Function(this, 'disconnect-lambda', { - code: new AssetCode('./ondisconnect'), - handler: 'app.handler', + const disconnectFunc = new Function(this, "disconnect-lambda", { + code: new AssetCode("./ondisconnect"), + handler: "app.handler", runtime: Runtime.NODEJS_12_X, timeout: Duration.seconds(30), memorySize: 256, environment: { - "TABLE_NAME": tableName, - } + TABLE_NAME: tableName, + }, }); + table.grantReadWriteData(disconnectFunc); - table.grantReadWriteData(disconnectFunc) - - const keepaliveFunc = new Function(this, 'keepalive-lambda', { - code: new AssetCode('./keepalive'), - handler: 'app.handler', + const keepaliveFunc = new Function(this, "keepalive-lambda", { + code: new AssetCode("./keepalive"), + handler: "app.handler", runtime: Runtime.NODEJS_12_X, timeout: Duration.seconds(2), memorySize: 128, }); - const messageFunc = new Function(this, 'message-lambda', { - code: new AssetCode('./sendmessage'), - handler: 'app.handler', + const messageFunc = new Function(this, "message-lambda", { + code: new AssetCode("./sendmessage"), + handler: "app.handler", runtime: Runtime.NODEJS_12_X, timeout: Duration.seconds(30), memorySize: 256, initialPolicy: [ new PolicyStatement({ - actions: [ - 'execute-api:ManageConnections' - ], + actions: ["execute-api:ManageConnections"], resources: [ - "arn:aws:execute-api:" + config["region"] + ":" + config["account_id"] + ":" + api.ref + "/*" + "arn:aws:execute-api:" + config["region"] + ":" + config["account_id"] + ":" + api.ref + "/*", ], effect: Effect.ALLOW, - }) + }), ], environment: { - "TABLE_NAME": tableName, - } + TABLE_NAME: tableName, + }, }); + table.grantReadWriteData(messageFunc); - table.grantReadWriteData(messageFunc) - - // access role for the socket api to access the socket lambda const policy = new PolicyStatement({ effect: Effect.ALLOW, resources: [ @@ -165,39 +156,38 @@ class ChatAppStack extends Stack { messageFunc.functionArn, keepaliveFunc.functionArn, ], - actions: ["lambda:InvokeFunction"] + actions: ["lambda:InvokeFunction"], }); const role = new Role(this, `${name}-iam-role`, { - assumedBy: new ServicePrincipal("apigateway.amazonaws.com") + assumedBy: new ServicePrincipal("apigateway.amazonaws.com"), }); role.addToPolicy(policy); - // lambda integration const connectIntegration = new CfnIntegration(this, "connect-lambda-integration", { apiId: api.ref, integrationType: "AWS_PROXY", integrationUri: "arn:aws:apigateway:" + config["region"] + ":lambda:path/2015-03-31/functions/" + connectFunc.functionArn + "/invocations", credentialsArn: role.roleArn, - }) + }); const disconnectIntegration = new CfnIntegration(this, "disconnect-lambda-integration", { apiId: api.ref, integrationType: "AWS_PROXY", integrationUri: "arn:aws:apigateway:" + config["region"] + ":lambda:path/2015-03-31/functions/" + disconnectFunc.functionArn + "/invocations", - credentialsArn: role.roleArn - }) + credentialsArn: role.roleArn, + }); const messageIntegration = new CfnIntegration(this, "message-lambda-integration", { apiId: api.ref, integrationType: "AWS_PROXY", integrationUri: "arn:aws:apigateway:" + config["region"] + ":lambda:path/2015-03-31/functions/" + messageFunc.functionArn + "/invocations", credentialsArn: role.roleArn, - }) + }); const keepaliveIntegration = new CfnIntegration(this, "keepalive-lambda-integration", { apiId: api.ref, integrationType: "AWS_PROXY", integrationUri: "arn:aws:apigateway:" + config["region"] + ":lambda:path/2015-03-31/functions/" + keepaliveFunc.functionArn + "/invocations", credentialsArn: role.roleArn, - }) + }); const connectRoute = new CfnRoute(this, "connect-route", { apiId: api.ref, @@ -228,24 +218,20 @@ class ChatAppStack extends Stack { }); const deployment = new CfnDeployment(this, `${name}-deployment`, { - apiId: api.ref + apiId: api.ref, }); new CfnStage(this, `${name}-stage`, { apiId: api.ref, autoDeploy: true, deploymentId: deployment.ref, - stageName: "v1" + stageName: "v1", }); - const dependencies = new ConcreteDependable(); - dependencies.add(connectRoute) - dependencies.add(disconnectRoute) - dependencies.add(messageRoute) - dependencies.add(keepaliveRoute); - deployment.node.addDependency(dependencies); + deployment.node.addDependency(connectRoute, disconnectRoute, messageRoute, keepaliveRoute); } } + const app = new App(); -new ChatAppStack(app, `abovevtt-backend`); -app.synth(); \ No newline at end of file +new ChatAppStack(app, "abovevtt-backend"); +app.synth(); diff --git a/package.json b/package.json index cbbb755..0e1f5bc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "api-websocket-lambda-dynamodb", "version": "1.0.0", - "description": "Use of an Application Load Balancer with an AutoScaling Group", + "description": "AboveVTT serverless backend", "private": true, "scripts": { "build": "tsc", @@ -15,14 +15,12 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/node": "^10.17.0", - "typescript": "~3.7.2" + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "~5.6.0" }, "dependencies": { - "@aws-cdk/aws-apigatewayv2": "^1.143.0", - "@aws-cdk/aws-apigatewayv2-integrations": "^1.143.0", - "@aws-cdk/aws-dynamodb": "^1.143.0", - "@aws-cdk/aws-lambda": "^1.143.0", - "@aws-cdk/core": "^1.143.0" + "aws-cdk-lib": "^2.200.0", + "constructs": "^10.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..815f8a2 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,225 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + aws-cdk-lib: + specifier: ^2.200.0 + version: 2.250.0(constructs@10.6.0) + constructs: + specifier: ^10.0.0 + version: 10.6.0 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.39 + ts-node: + specifier: ^10.9.0 + version: 10.9.2(@types/node@20.19.39)(typescript@5.6.3) + typescript: + specifier: ~5.6.0 + version: 5.6.3 + +packages: + + '@aws-cdk/asset-awscli-v1@2.2.273': + resolution: {integrity: sha512-X57HYUtHt9BQrlrzUNcMyRsDUCoakYNnY6qh5lNwRCHPtQoTfXmuISkfLk0AjLkcbS5lw1LLTQFiQhTDXfiTvg==} + + '@aws-cdk/asset-node-proxy-agent-v6@2.1.1': + resolution: {integrity: sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==} + + '@aws-cdk/cloud-assembly-schema@53.17.0': + resolution: {integrity: sha512-JB9L4JCbZeYbMJPs7OKV+7YaxKwk4i2tlI5PCSfCzk4DJvprhCO2TYcqbqo3Y1YWZyyY/SEPLWdorG7b88fUSA==} + engines: {node: '>= 18.0.0'} + bundledDependencies: + - jsonschema + - semver + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/node@20.19.39': + resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + aws-cdk-lib@2.250.0: + resolution: {integrity: sha512-8U8/S9VcmKSc3MHZWiB7P0IecgXoohI8Ya3dgtZMgbzC4mB+MEQmsYBeNgm4vzGQdRos8HjQLnFX1IBlZh7jQA==} + engines: {node: '>= 20.0.0'} + peerDependencies: + constructs: ^10.5.0 + bundledDependencies: + - '@balena/dockerignore' + - '@aws-cdk/cloud-assembly-api' + - case + - fs-extra + - ignore + - jsonschema + - minimatch + - punycode + - semver + - table + - yaml + - mime-types + + constructs@10.6.0: + resolution: {integrity: sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + +snapshots: + + '@aws-cdk/asset-awscli-v1@2.2.273': {} + + '@aws-cdk/asset-node-proxy-agent-v6@2.1.1': {} + + '@aws-cdk/cloud-assembly-schema@53.17.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/node@20.19.39': + dependencies: + undici-types: 6.21.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + arg@4.1.3: {} + + aws-cdk-lib@2.250.0(constructs@10.6.0): + dependencies: + '@aws-cdk/asset-awscli-v1': 2.2.273 + '@aws-cdk/asset-node-proxy-agent-v6': 2.1.1 + '@aws-cdk/cloud-assembly-schema': 53.17.0 + constructs: 10.6.0 + + constructs@10.6.0: {} + + create-require@1.1.1: {} + + diff@4.0.4: {} + + make-error@1.3.6: {} + + ts-node@10.9.2(@types/node@20.19.39)(typescript@5.6.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.39 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.6.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + typescript@5.6.3: {} + + undici-types@6.21.0: {} + + v8-compile-cache-lib@3.0.1: {} + + yn@3.1.1: {} diff --git a/tsconfig.json b/tsconfig.json index 2cc3e66..cec0288 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target":"ES2018", + "target": "ES2020", "module": "commonjs", - "lib": ["es2016", "es2017.object", "es2017.string"], + "lib": ["es2020"], "strict": true, "noImplicitAny": true, "strictNullChecks": true, @@ -11,13 +11,13 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, + "noFallthroughCasesInSwitch": true, "inlineSourceMap": true, "inlineSources": true, "experimentalDecorators": true, - "strictPropertyInitialization":false, + "strictPropertyInitialization": false, "resolveJsonModule": true, "esModuleInterop": true }, - "exclude": ["cdk.out"] + "exclude": ["cdk.out", "node_modules"] }