From ebcd1cafd4c6eda6e2007110fe1bfb5fbcee1d48 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:14:07 -0800 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20dynamodb=20libs?= =?UTF-8?q?=20and=20table=20to=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/bronya.config.ts | 9 +- apps/api/package.json | 2 + pnpm-lock.yaml | 168 +++++++++++++++++++++++++++----------- 3 files changed, 129 insertions(+), 50 deletions(-) diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index fb9edc20..f7c6658a 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -8,12 +8,13 @@ import { PrismaClient } from "@libs/db"; import { logger, warmingRequestBody } from "@libs/lambda"; import { LambdaIntegration, ResponseType } from "aws-cdk-lib/aws-apigateway"; import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; -import { RuleTargetInput, Rule, Schedule } from "aws-cdk-lib/aws-events"; +import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb"; +import { Rule, RuleTargetInput, Schedule } from "aws-cdk-lib/aws-events"; import { LambdaFunction } from "aws-cdk-lib/aws-events-targets"; import { Architecture, Code, Function as AwsLambdaFunction, Runtime } from "aws-cdk-lib/aws-lambda"; import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; import { ApiGateway } from "aws-cdk-lib/aws-route53-targets"; -import { App, Stack, Duration } from "aws-cdk-lib/core"; +import { App, Duration, Stack } from "aws-cdk-lib/core"; import { config } from "dotenv"; import type { BuildOptions } from "esbuild"; @@ -171,6 +172,7 @@ export const constructs: ApiConstructProps = { */ class ApiStack extends Stack { public api: Api; + public cache: Table; constructor(scope: App, id: string, stage: string) { super(scope, id); @@ -205,6 +207,9 @@ class ApiStack extends Stack { }, esbuild: esbuildOptions, }); + this.cache = new Table(this, `${id}-${stage}-cache`, { + partitionKey: { name: "cacheKey", type: AttributeType.STRING }, + }); } } diff --git a/apps/api/package.json b/apps/api/package.json index fb249eef..b5859dcf 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,7 +21,9 @@ }, "dependencies": { "@apollo/server": "4.10.0", + "@aws-sdk/client-dynamodb": "3.501.0", "@aws-sdk/client-lambda": "3.501.0", + "@aws-sdk/lib-dynamodb": "3.501.0", "@graphql-tools/load-files": "7.0.0", "@graphql-tools/merge": "9.0.1", "@graphql-tools/utils": "10.0.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2a0c79f..743c78c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,9 +89,15 @@ importers: '@apollo/server': specifier: 4.10.0 version: 4.10.0(graphql@16.8.1) + '@aws-sdk/client-dynamodb': + specifier: 3.501.0 + version: 3.501.0 '@aws-sdk/client-lambda': specifier: 3.501.0 version: 3.501.0 + '@aws-sdk/lib-dynamodb': + specifier: 3.501.0 + version: 3.501.0(@aws-sdk/client-dynamodb@3.501.0) '@graphql-tools/load-files': specifier: 7.0.0 version: 7.0.0(graphql@16.8.1) @@ -789,7 +795,7 @@ packages: resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.496.0 + '@aws-sdk/types': 3.502.0 tslib: 1.14.1 dev: false @@ -885,6 +891,57 @@ packages: - aws-crt dev: false + /@aws-sdk/client-dynamodb@3.501.0: + resolution: {integrity: sha512-yPcxGnj6P87ye/mfTqwv3i29T3HO4eOGl2IBF8ZB5gQtOIctwg66TWBKCPG8HRbLKJgIVEvE20MiOELvuiUQVg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.501.0 + '@aws-sdk/core': 3.496.0 + '@aws-sdk/credential-provider-node': 3.501.0 + '@aws-sdk/middleware-endpoint-discovery': 3.496.0 + '@aws-sdk/middleware-host-header': 3.496.0 + '@aws-sdk/middleware-logger': 3.496.0 + '@aws-sdk/middleware-recursion-detection': 3.496.0 + '@aws-sdk/middleware-user-agent': 3.496.0 + '@aws-sdk/region-config-resolver': 3.496.0 + '@aws-sdk/types': 3.496.0 + '@aws-sdk/util-endpoints': 3.496.0 + '@aws-sdk/util-user-agent-browser': 3.496.0 + '@aws-sdk/util-user-agent-node': 3.496.0 + '@smithy/config-resolver': 2.1.1 + '@smithy/core': 1.3.1 + '@smithy/fetch-http-handler': 2.4.1 + '@smithy/hash-node': 2.1.1 + '@smithy/invalid-dependency': 2.1.1 + '@smithy/middleware-content-length': 2.1.1 + '@smithy/middleware-endpoint': 2.4.1 + '@smithy/middleware-retry': 2.1.1 + '@smithy/middleware-serde': 2.1.1 + '@smithy/middleware-stack': 2.1.1 + '@smithy/node-config-provider': 2.2.1 + '@smithy/node-http-handler': 2.3.1 + '@smithy/protocol-http': 3.1.1 + '@smithy/smithy-client': 2.3.1 + '@smithy/types': 2.9.1 + '@smithy/url-parser': 2.1.1 + '@smithy/util-base64': 2.1.1 + '@smithy/util-body-length-browser': 2.1.1 + '@smithy/util-body-length-node': 2.2.1 + '@smithy/util-defaults-mode-browser': 2.1.1 + '@smithy/util-defaults-mode-node': 2.1.1 + '@smithy/util-endpoints': 1.1.1 + '@smithy/util-middleware': 2.1.1 + '@smithy/util-retry': 2.1.1 + '@smithy/util-utf8': 2.1.1 + '@smithy/util-waiter': 2.1.1 + tslib: 2.6.2 + uuid: 8.3.2 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/client-lambda@3.501.0: resolution: {integrity: sha512-RkuNt5JalmPGxy3Nz73U9B0ArqVqkLLIP5BMvMlR9ghmrZWFaY/5AiIDUmz8xtfJjL9FHzdtt2/JcIYtAbrpeA==} engines: {node: '>=14.0.0'} @@ -1126,6 +1183,39 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/endpoint-cache@3.495.0: + resolution: {integrity: sha512-XCDrpiS50WaPzPzp7FwsChPHtX9PQQUU4nRzcn2N7IkUtpcFCUx8m1PAZe086VQr6hrbdeE4Z4j8hUPNwVdJGQ==} + engines: {node: '>=14.0.0'} + dependencies: + mnemonist: 0.38.3 + tslib: 2.6.2 + dev: false + + /@aws-sdk/lib-dynamodb@3.501.0(@aws-sdk/client-dynamodb@3.501.0): + resolution: {integrity: sha512-2GJgPtHuRJto8bm87Kr/ztk/SJoLc0aOtUKA8v46izm5nnhUMx4M/wKZQjnuQXV8eWTxHQdDo00qDdOlubQ1NQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.0.0 + dependencies: + '@aws-sdk/client-dynamodb': 3.501.0 + '@aws-sdk/util-dynamodb': 3.501.0(@aws-sdk/client-dynamodb@3.501.0) + '@smithy/smithy-client': 2.3.1 + '@smithy/types': 2.9.1 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-endpoint-discovery@3.496.0: + resolution: {integrity: sha512-VVJFTYeyhtjY8c0sAQUmzN4OUzaPINvXsYcL1dljFd7vdgSyf7+qwxzngOH5pKvyaCZ2vq9ngd8dAynSlA+TIg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/endpoint-cache': 3.495.0 + '@aws-sdk/types': 3.496.0 + '@smithy/node-config-provider': 2.2.1 + '@smithy/protocol-http': 3.1.1 + '@smithy/types': 2.9.1 + tslib: 2.6.2 + dev: false + /@aws-sdk/middleware-host-header@3.496.0: resolution: {integrity: sha512-jUdPpSJeqCYXf6hSjfwsfHway7peIV8Vz51w/BN91bF4vB/bYwAC5o9/iJiK/EoByp5asxA8fg9wFOyGjzdbLg==} engines: {node: '>=14.0.0'} @@ -1244,6 +1334,24 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/types@3.502.0: + resolution: {integrity: sha512-M0DSPYe/gXhwD2QHgoukaZv5oDxhW3FfvYIrJptyqUq3OnPJBcDbihHjrE0PBtfh/9kgMZT60/fQ2NVFANfa2g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.9.1 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-dynamodb@3.501.0(@aws-sdk/client-dynamodb@3.501.0): + resolution: {integrity: sha512-ywBTyScgHqtKJgpPjWW/63lNpRQ53rMkqLGLzcY2NQ6t2JJ2AREk+mZSMEGhUSddqO+zRk+k0uV4BSnLyHY0kQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.0.0 + dependencies: + '@aws-sdk/client-dynamodb': 3.501.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/util-endpoints@3.496.0: resolution: {integrity: sha512-1QzOiWHi383ZwqSi/R2KgKCd7M+6DxkxI5acqLPm8mvDRDP2jRjrnVaC0g9/tlttWousGEemDUWStwrD2mVYSw==} engines: {node: '>=14.0.0'} @@ -2802,16 +2910,6 @@ packages: conventional-changelog-conventionalcommits: 7.0.2 dev: true - /@commitlint/config-validator@18.4.4: - resolution: {integrity: sha512-/QI8KIg/h7O0Eus36fPcEcO3QPBcdXuGfZeCF5m15k0EB2bcU8s6pHNTNEa6xz9PrAefHCL+yzRJj7w20T6Mow==} - engines: {node: '>=v18'} - requiresBuild: true - dependencies: - '@commitlint/types': 18.6.0 - ajv: 8.12.0 - dev: true - optional: true - /@commitlint/config-validator@18.6.0: resolution: {integrity: sha512-Ptfa865arNozlkjxrYG3qt6wT9AlhNUHeuDyKEZiTL/l0ftncFhK/KN0t/EAMV2tec+0Mwxo0FmhbESj/bI+1g==} engines: {node: '>=v18'} @@ -2863,28 +2961,6 @@ packages: '@commitlint/types': 18.6.0 dev: true - /@commitlint/load@18.4.4(@types/node@20.11.10)(typescript@5.3.3): - resolution: {integrity: sha512-RaDIa9qwOw2xRJ3Jr2DBXd14rmnHJIX2XdZF4kmoF1rgsg/+7cvrExLSUNAkQUNimyjCn1b/bKX2Omm+GdY0XQ==} - engines: {node: '>=v18'} - requiresBuild: true - dependencies: - '@commitlint/config-validator': 18.4.4 - '@commitlint/execute-rule': 18.4.4 - '@commitlint/resolve-extends': 18.4.4 - '@commitlint/types': 18.6.0 - chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.3.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.10)(cosmiconfig@8.3.6)(typescript@5.3.3) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 - resolve-from: 5.0.0 - transitivePeerDependencies: - - '@types/node' - - typescript - dev: true - optional: true - /@commitlint/load@18.6.0(@types/node@20.11.10)(typescript@5.3.3): resolution: {integrity: sha512-RRssj7TmzT0bowoEKlgwg8uQ7ORXWkw7lYLsZZBMi9aInsJuGNLNWcMxJxRZbwxG3jkCidGUg85WmqJvRjsaDA==} engines: {node: '>=v18'} @@ -2929,20 +3005,6 @@ packages: minimist: 1.2.8 dev: true - /@commitlint/resolve-extends@18.4.4: - resolution: {integrity: sha512-RRpIHSbRnFvmGifVk21Gqazf1QF/yeP+Kkg/e3PlkegcOKd/FGOXp/Kx9cvSO2K7ucSn4GD/oBvgasFoy+NCAw==} - engines: {node: '>=v18'} - requiresBuild: true - dependencies: - '@commitlint/config-validator': 18.4.4 - '@commitlint/types': 18.6.0 - import-fresh: 3.3.0 - lodash.mergewith: 4.6.2 - resolve-from: 5.0.0 - resolve-global: 1.0.0 - dev: true - optional: true - /@commitlint/resolve-extends@18.6.0: resolution: {integrity: sha512-k2Xp+Fxeggki2i90vGrbiLDMefPius3zGSTFFlRAPKce/SWLbZtI+uqE9Mne23mHO5lmcSV8z5m6ziiJwGpOcg==} engines: {node: '>=v18'} @@ -7259,7 +7321,7 @@ packages: longest: 2.0.1 word-wrap: 1.2.3 optionalDependencies: - '@commitlint/load': 18.4.4(@types/node@20.11.10)(typescript@5.3.3) + '@commitlint/load': 18.6.0(@types/node@20.11.10)(typescript@5.3.3) transitivePeerDependencies: - '@types/node' - typescript @@ -10191,6 +10253,12 @@ packages: ufo: 1.3.1 dev: true + /mnemonist@0.38.3: + resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + dependencies: + obliterator: 1.6.1 + dev: false + /moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} dev: true @@ -10418,6 +10486,10 @@ packages: es-abstract: 1.22.3 dev: true + /obliterator@1.6.1: + resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + dev: false + /obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} dev: false From ba0716080bc90cce658142c41383ea6628619fd8 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:40:20 -0800 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=E2=9C=A8=20test=20role=20stuff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/bronya.config.ts | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index f7c6658a..9daffa74 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -11,6 +11,14 @@ import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb"; import { Rule, RuleTargetInput, Schedule } from "aws-cdk-lib/aws-events"; import { LambdaFunction } from "aws-cdk-lib/aws-events-targets"; +import { + Effect, + ManagedPolicy, + PolicyDocument, + PolicyStatement, + Role, + ServicePrincipal, +} from "aws-cdk-lib/aws-iam"; import { Architecture, Code, Function as AwsLambdaFunction, Runtime } from "aws-cdk-lib/aws-lambda"; import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; import { ApiGateway } from "aws-cdk-lib/aws-route53-targets"; @@ -142,7 +150,28 @@ export const esbuildOptions: BuildOptions = { * Shared construct props. */ export const constructs: ApiConstructProps = { - functionProps: () => ({ runtime: Runtime.NODEJS_20_X }), + functionProps: (scope, id) => ({ + runtime: Runtime.NODEJS_20_X, + role: new Role(scope, `${id}-role`, { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ], + inlinePolicies: { + lambdaInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, + ], + actions: ["lambda:InvokeFunction"], + }), + ], + }), + }, + }), + }), functionPlugin: ({ functionProps, handler }, scope) => { const warmingTarget = new LambdaFunction(handler, { event: RuleTargetInput.fromObject(warmingRequestBody), @@ -207,8 +236,9 @@ class ApiStack extends Stack { }, esbuild: esbuildOptions, }); - this.cache = new Table(this, `${id}-${stage}-cache`, { + this.cache = new Table(this, `${id}-cache`, { partitionKey: { name: "cacheKey", type: AttributeType.STRING }, + timeToLiveAttribute: "expireAt", }); } } From 4c8da168fbd500da1738dd3c4c4b88d79fefbd81 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:12:25 -0800 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=E2=9C=A8=20temp=20kludge=20for?= =?UTF-8?q?=20random=20role=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/bronya.config.ts | 40 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index 9daffa74..6bd99da6 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -152,25 +152,29 @@ export const esbuildOptions: BuildOptions = { export const constructs: ApiConstructProps = { functionProps: (scope, id) => ({ runtime: Runtime.NODEJS_20_X, - role: new Role(scope, `${id}-role`, { - assumedBy: new ServicePrincipal("lambda.amazonaws.com"), - managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), - ], - inlinePolicies: { - lambdaInvokePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [ - `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, - ], - actions: ["lambda:InvokeFunction"], - }), - ], - }), + role: new Role( + scope, + `${id}-${Number.parseInt(Math.random().toString().slice(2)).toString(16).padStart(14, "0")}-role`, + { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ], + inlinePolicies: { + lambdaInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, + ], + actions: ["dynamodb:GetItem", "dynamodb:PutItem"], + }), + ], + }), + }, }, - }), + ), }), functionPlugin: ({ functionProps, handler }, scope) => { const warmingTarget = new LambdaFunction(handler, { From 20ac2c2f31b9539e39b259442143994ee302bc9d Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:13:32 -0800 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20ddb=20role=20to?= =?UTF-8?q?=20websoc=20endpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/+config.ts | 7 +++++++ apps/api/src/routes/v1/rest/websoc/{id}/+config.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/apps/api/src/routes/v1/rest/websoc/+config.ts b/apps/api/src/routes/v1/rest/websoc/+config.ts index 2367b871..e6a2b839 100644 --- a/apps/api/src/routes/v1/rest/websoc/+config.ts +++ b/apps/api/src/routes/v1/rest/websoc/+config.ts @@ -30,6 +30,13 @@ export const overrides: ApiPropsOverride = { ], actions: ["lambda:InvokeFunction"], }), + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, + ], + actions: ["dynamodb:GetItem", "dynamodb:PutItem"], + }), ], }), }, diff --git a/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts b/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts index edcbedde..e411e9dd 100644 --- a/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts +++ b/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts @@ -30,6 +30,13 @@ export const overrides: ApiPropsOverride = { ], actions: ["lambda:InvokeFunction"], }), + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, + ], + actions: ["dynamodb:GetItem", "dynamodb:PutItem"], + }), ], }), }, From f75f2784bd84919aa912166e64d66b72d070c6e5 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:20:09 -0800 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=E2=9C=A8=20use=20on-demand=20cap?= =?UTF-8?q?acity=20so=20we=20don't=20go=20broke?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/bronya.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index 6bd99da6..3cb65887 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -8,7 +8,7 @@ import { PrismaClient } from "@libs/db"; import { logger, warmingRequestBody } from "@libs/lambda"; import { LambdaIntegration, ResponseType } from "aws-cdk-lib/aws-apigateway"; import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; -import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb"; +import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb"; import { Rule, RuleTargetInput, Schedule } from "aws-cdk-lib/aws-events"; import { LambdaFunction } from "aws-cdk-lib/aws-events-targets"; import { @@ -243,6 +243,7 @@ class ApiStack extends Stack { this.cache = new Table(this, `${id}-cache`, { partitionKey: { name: "cacheKey", type: AttributeType.STRING }, timeToLiveAttribute: "expireAt", + billingMode: BillingMode.PAY_PER_REQUEST, }); } } From c7c7dd151f6be7d1f324c2c46ca3c702a97077d6 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:52:37 -0800 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=E2=9C=A8=20create=20cache=20clie?= =?UTF-8?q?nt=20&=20integrate=20with=20grades/websoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/package.json | 3 +- .../routes/v1/rest/grades/{id}/+endpoint.ts | 118 +++++++++++------- .../src/routes/v1/rest/websoc/+endpoint.ts | 21 +++- libs/cache-client/package.json | 13 ++ libs/cache-client/src/index.ts | 56 +++++++++ pnpm-lock.yaml | 18 ++- 6 files changed, 170 insertions(+), 59 deletions(-) create mode 100644 libs/cache-client/package.json create mode 100644 libs/cache-client/src/index.ts diff --git a/apps/api/package.json b/apps/api/package.json index b5859dcf..235390c3 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,12 +21,11 @@ }, "dependencies": { "@apollo/server": "4.10.0", - "@aws-sdk/client-dynamodb": "3.501.0", "@aws-sdk/client-lambda": "3.501.0", - "@aws-sdk/lib-dynamodb": "3.501.0", "@graphql-tools/load-files": "7.0.0", "@graphql-tools/merge": "9.0.1", "@graphql-tools/utils": "10.0.13", + "@libs/cache-client": "workspace:^", "@libs/db": "workspace:^", "@libs/lambda": "workspace:^", "@libs/uc-irvine-api": "workspace:^", diff --git a/apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts b/apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts index ba76cfea..cecd8e20 100644 --- a/apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts @@ -1,3 +1,4 @@ +import { APICacheClient } from "@libs/cache-client"; import { PrismaClient } from "@libs/db"; import { logger, createHandler } from "@libs/lambda"; import type { @@ -20,35 +21,49 @@ import { QuerySchema } from "./schema"; const prisma = new PrismaClient(); +const cacheClient = new APICacheClient({ route: "/v1/rest/grades" }); + async function onWarm() { await prisma.$connect(); } export const GET = createHandler(async (event, context, res) => { - const { headers, pathParameters: params, queryStringParameters: query } = event; + const { headers, pathParameters: params } = event; + const query = event.queryStringParameters ?? {}; const { awsRequestId: requestId } = context; try { const parsedQuery = QuerySchema.parse(query); switch (params?.id) { case "raw": - case "aggregate": - { - const result = ( - await prisma.gradesSection.findMany({ - where: constructPrismaQuery(parsedQuery), - include: { instructors: true }, - }) - ).map(transformRow); - switch (params.id) { - case "raw": - return res.createOKResult(result, headers, requestId); - case "aggregate": - return res.createOKResult(aggregateGrades(result), headers, requestId); - } + case "aggregate": { + const cacheResult = await cacheClient.get({ ...query, id: params.id }); + if (cacheResult) return res.createOKResult(cacheResult, headers, requestId); + const result = ( + await prisma.gradesSection.findMany({ + where: constructPrismaQuery(parsedQuery), + include: { instructors: true }, + }) + ).map(transformRow); + switch (params.id) { + case "raw": + return res.createOKResult( + await cacheClient.put({ ...query, id: params.id }, result), + headers, + requestId, + ); + case "aggregate": + return res.createOKResult( + await cacheClient.put({ ...query, id: params.id }, aggregateGrades(result)), + headers, + requestId, + ); } - break; + } + // eslint-disable-next-line no-fallthrough case "options": { + const cacheResult = await cacheClient.get({ ...query, id: params.id }); + if (cacheResult) return res.createOKResult(cacheResult, headers, requestId); const result = await prisma.gradesSection.findMany({ where: constructPrismaQuery(parsedQuery), select: { @@ -79,36 +94,42 @@ export const GET = createHandler(async (event, context, res) => { sectionCodes: Array.from(sectionCodes).sort(), }; return res.createOKResult( - { - ...ret, - instructors: parsedQuery.instructor - ? [parsedQuery.instructor] - : ( - await prisma.gradesInstructor.findMany({ - where: { - year: { in: ret.years }, - sectionCode: { in: ret.sectionCodes }, - }, - select: { name: true }, - distinct: ["name"], - }) - ) - .map((x) => x.name) - .sort(), - }, + await cacheClient.put( + { ...query, id: params.id }, + { + ...ret, + instructors: parsedQuery.instructor + ? [parsedQuery.instructor] + : ( + await prisma.gradesInstructor.findMany({ + where: { + year: { in: ret.years }, + sectionCode: { in: ret.sectionCodes }, + }, + select: { name: true }, + distinct: ["name"], + }) + ) + .map((x) => x.name) + .sort(), + }, + ), headers, requestId, ); } case "aggregateByCourse": { return res.createOKResult( - aggregateByCourse( - ( - await prisma.gradesSection.findMany({ - where: constructPrismaQuery(parsedQuery), - include: { instructors: true }, - }) - ).map(transformRow), + await cacheClient.put( + { ...query, id: params.id }, + aggregateByCourse( + ( + await prisma.gradesSection.findMany({ + where: constructPrismaQuery(parsedQuery), + include: { instructors: true }, + }) + ).map(transformRow), + ), ), headers, requestId, @@ -116,13 +137,16 @@ export const GET = createHandler(async (event, context, res) => { } case "aggregateByOffering": { return res.createOKResult( - aggregateByOffering( - ( - await prisma.gradesSection.findMany({ - where: constructPrismaQuery(parsedQuery), - include: { instructors: true }, - }) - ).map(transformRow), + await cacheClient.put( + { ...query, id: params.id }, + aggregateByOffering( + ( + await prisma.gradesSection.findMany({ + where: constructPrismaQuery(parsedQuery), + include: { instructors: true }, + }) + ).map(transformRow), + ), ), headers, requestId, diff --git a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts index f6fa9358..a588e7dd 100644 --- a/apps/api/src/routes/v1/rest/websoc/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -1,3 +1,4 @@ +import { APICacheClient } from "@libs/cache-client"; import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; import type { WebsocAPIResponse } from "@libs/uc-irvine-api/websoc"; @@ -10,22 +11,26 @@ import { QuerySchema } from "./schema"; const prisma = new PrismaClient(); -// let connected = false const lambdaClient = await APILambdaClient.new(); +const cacheClient = new APICacheClient({ route: "/v1/rest/websoc" }); + async function onWarm() { await prisma.$connect(); } export const GET = createHandler(async (event, context, res) => { const headers = event.headers; - const query = event.queryStringParameters; + const query = event.queryStringParameters ?? {}; const requestId = context.awsRequestId; try { const parsedQuery = QuerySchema.parse(query); if (parsedQuery.cache) { + const cacheResult = await cacheClient.get(query); + if (cacheResult) return res.createOKResult(cacheResult, headers, requestId); + const websocSections = await prisma.websocSection.findMany({ where: constructPrismaQuery(parsedQuery), select: { department: true, courseNumber: true, data: true }, @@ -82,7 +87,11 @@ export const GET = createHandler(async (event, context, res) => { const combinedResponses = combineAndNormalizeResponses(...responses); - return res.createOKResult(sortResponse(combinedResponses), headers, requestId); + return res.createOKResult( + await cacheClient.put(query, sortResponse(combinedResponses)), + headers, + requestId, + ); } const websocApiResponses = websocSections @@ -91,7 +100,11 @@ export const GET = createHandler(async (event, context, res) => { const combinedResponses = combineAndNormalizeResponses(...websocApiResponses); - return res.createOKResult(sortResponse(combinedResponses), headers, requestId); + return res.createOKResult( + await cacheClient.put(query, sortResponse(combinedResponses)), + headers, + requestId, + ); } /** diff --git a/libs/cache-client/package.json b/libs/cache-client/package.json new file mode 100644 index 00000000..0e531d76 --- /dev/null +++ b/libs/cache-client/package.json @@ -0,0 +1,13 @@ +{ + "name": "@libs/cache-client", + "version": "0.0.0", + "private": true, + "license": "MIT", + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "dependencies": { + "@aws-sdk/client-dynamodb": "3.501.0", + "@aws-sdk/lib-dynamodb": "3.501.0" + } +} diff --git a/libs/cache-client/src/index.ts b/libs/cache-client/src/index.ts new file mode 100644 index 00000000..c03271f3 --- /dev/null +++ b/libs/cache-client/src/index.ts @@ -0,0 +1,56 @@ +import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; +import { DynamoDBDocumentClient, GetCommand, PutCommand } from "@aws-sdk/lib-dynamodb"; + +export class APICacheClient { + private readonly docClient; + private readonly route; + private readonly tableName; + private readonly ttlMillis; + + constructor({ + route, + tableName, + ttlMillis, + }: { + route: string; + tableName?: string; + ttlMillis?: number; + }) { + this.docClient = DynamoDBDocumentClient.from(new DynamoDBClient({})); + this.route = route; + this.tableName = tableName ?? `peterportal-api-next-${process.env.STAGE}-cache`; + this.ttlMillis = ttlMillis ?? 5 * 60 * 1000; + } + + private getCacheKey(params: Record): string { + return `${this.route}?${Object.entries(params) + .sort() + .map(([k, v]) => `${k}=${v}`) + .join("&")}`; + } + + async get(params: Record): Promise { + return await this.docClient + .send( + new GetCommand({ + TableName: this.tableName, + Key: { cacheKey: this.getCacheKey(params) }, + }), + ) + .then((x) => x.Item?.payload); + } + + async put(params: Record, payload: T): Promise { + await this.docClient.send( + new PutCommand({ + TableName: this.tableName, + Item: { + cacheKey: this.getCacheKey(params), + payload, + expireAt: Date.now() + this.ttlMillis, + }, + }), + ); + return payload; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 743c78c8..5696f045 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,15 +89,9 @@ importers: '@apollo/server': specifier: 4.10.0 version: 4.10.0(graphql@16.8.1) - '@aws-sdk/client-dynamodb': - specifier: 3.501.0 - version: 3.501.0 '@aws-sdk/client-lambda': specifier: 3.501.0 version: 3.501.0 - '@aws-sdk/lib-dynamodb': - specifier: 3.501.0 - version: 3.501.0(@aws-sdk/client-dynamodb@3.501.0) '@graphql-tools/load-files': specifier: 7.0.0 version: 7.0.0(graphql@16.8.1) @@ -107,6 +101,9 @@ importers: '@graphql-tools/utils': specifier: 10.0.13 version: 10.0.13(graphql@16.8.1) + '@libs/cache-client': + specifier: workspace:^ + version: link:../../libs/cache-client '@libs/db': specifier: workspace:^ version: link:../../libs/db @@ -221,6 +218,15 @@ importers: specifier: 5.3.3 version: 5.3.3 + libs/cache-client: + dependencies: + '@aws-sdk/client-dynamodb': + specifier: 3.501.0 + version: 3.501.0 + '@aws-sdk/lib-dynamodb': + specifier: 3.501.0 + version: 3.501.0(@aws-sdk/client-dynamodb@3.501.0) + libs/db: dependencies: '@prisma/client': From dfb651110ca2c8d674afd86ba86e7693e3ac3c3d Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:53:04 -0800 Subject: [PATCH 07/10] =?UTF-8?q?chore(deps):=20=F0=9F=94=97=20update=20br?= =?UTF-8?q?onya?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 235390c3..b23d0c1d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -39,8 +39,8 @@ "zod": "3.22.4" }, "devDependencies": { - "@bronya.js/api-construct": "0.11.3", - "@bronya.js/core": "0.11.3", + "@bronya.js/api-construct": "0.11.4", + "@bronya.js/core": "0.11.4", "@types/aws-lambda": "8.10.132", "aws-cdk": "2.124.0", "dotenv": "16.4.1", From 7f1cc2e0b32183a4193bb482b28179f47ca07742 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:01:26 -0800 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=E2=9C=A8=20remove=20RNG=20kludge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/bronya.config.ts | 42 ++++++++++++++++++--------------------- pnpm-lock.yaml | 35 ++++++++++++++------------------ 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index 3cb65887..16228f6d 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -150,31 +150,27 @@ export const esbuildOptions: BuildOptions = { * Shared construct props. */ export const constructs: ApiConstructProps = { - functionProps: (scope, id) => ({ + functionProps: (scope, id, route) => ({ runtime: Runtime.NODEJS_20_X, - role: new Role( - scope, - `${id}-${Number.parseInt(Math.random().toString().slice(2)).toString(16).padStart(14, "0")}-role`, - { - assumedBy: new ServicePrincipal("lambda.amazonaws.com"), - managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), - ], - inlinePolicies: { - lambdaInvokePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [ - `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, - ], - actions: ["dynamodb:GetItem", "dynamodb:PutItem"], - }), - ], - }), - }, + role: new Role(scope, `${id}-${route.endpoint}-role`, { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ], + inlinePolicies: { + lambdaInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:dynamodb:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:table/${id}-cache`, + ], + actions: ["dynamodb:GetItem", "dynamodb:PutItem"], + }), + ], + }), }, - ), + }), }), functionPlugin: ({ functionProps, handler }, scope) => { const warmingTarget = new LambdaFunction(handler, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5696f045..0b92ee45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,11 +139,11 @@ importers: version: 3.22.4 devDependencies: '@bronya.js/api-construct': - specifier: 0.11.3 - version: 0.11.3 + specifier: 0.11.4 + version: 0.11.4 '@bronya.js/core': - specifier: 0.11.3 - version: 0.11.3 + specifier: 0.11.4 + version: 0.11.4 '@types/aws-lambda': specifier: 8.10.132 version: 8.10.132 @@ -2830,12 +2830,12 @@ packages: to-fast-properties: 2.0.0 dev: false - /@bronya.js/api-construct@0.11.3: - resolution: {integrity: sha512-ro5/IwJQIv36k0uoYYL/pMzvFYJMlnzBOdeDylet+9T7gbXSjZEt+LKA9QjFGoqDvRl3WOQIJLUfJRollOVXng==} + /@bronya.js/api-construct@0.11.4: + resolution: {integrity: sha512-zR5G9wn9IYzy4T68fnh6S6helxjc2eaf6c0emyqY3Z6H4HpSNttK1XBnFRC+yUThYJDpcisvM3LK0ia8z+chuQ==} engines: {node: '>=18', pnpm: ^8.0.0} dependencies: - '@bronya.js/cli': 0.11.3 - '@bronya.js/core': 0.11.3 + '@bronya.js/cli': 0.11.4 + '@bronya.js/core': 0.11.4 acorn: 8.10.0 acorn-typescript: 1.4.5(acorn@8.10.0) aws-cdk-lib: 2.124.0(constructs@10.2.69) @@ -2847,22 +2847,22 @@ packages: defu: 6.1.2 express: 4.18.2 fs-extra: 11.1.1 - jiti: 1.19.1 + jiti: 1.20.0 transitivePeerDependencies: - supports-color dev: true - /@bronya.js/cli@0.11.3: - resolution: {integrity: sha512-1FkvlcXR17rfSGo7cDScrxCeC1S3Ib7GJaGTa874tOtrVHZVM7T0ebyPI1lO715BVEvS+hursJElRU9MZsnQOQ==} + /@bronya.js/cli@0.11.4: + resolution: {integrity: sha512-0aABMJmIhQiIl6gqn2dcXtQCme5RC3BcN1syDxI72H3+RJrKV7Reusw/R8z5ev9lUKc6kPay+OmD7Ck2z0zYCg==} engines: {node: '>=18', pnpm: ^8.0.0} dev: true - /@bronya.js/core@0.11.3: - resolution: {integrity: sha512-D5RU9y6o7KSmUjewjPR9ImR61/FQG4Xw42onn2vwiIJzCyQ7qzo1c7ecnbh7AVdldwa698kySRGne5Um/AemOA==} + /@bronya.js/core@0.11.4: + resolution: {integrity: sha512-DCRxoGsE8a63f5G/bslYi7MK3xPO6j70vXrxUEwRq5k2CtT54KcHejySzkSg3eA7FSes5l4K4LZk8yPHGi9TAQ==} engines: {node: '>=18', pnpm: ^8.0.0} hasBin: true dependencies: - '@bronya.js/cli': 0.11.3 + '@bronya.js/cli': 0.11.4 acorn: 8.10.0 acorn-typescript: 1.4.5(acorn@8.10.0) aws-cdk-lib: 2.124.0(constructs@10.2.69) @@ -2873,7 +2873,7 @@ packages: cors: 2.8.5 defu: 6.1.2 express: 4.18.2 - jiti: 1.19.1 + jiti: 1.20.0 transitivePeerDependencies: - supports-color dev: true @@ -9659,11 +9659,6 @@ packages: supports-color: 8.1.1 dev: false - /jiti@1.19.1: - resolution: {integrity: sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==} - hasBin: true - dev: true - /jiti@1.20.0: resolution: {integrity: sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==} hasBin: true From 7a0c270433a5885085fc53f0404ae34be838b38d Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:05:40 -0800 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20websoc=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/routes/v1/rest/websoc/+config.ts | 4 ++-- apps/api/src/routes/v1/rest/websoc/{id}/+config.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/v1/rest/websoc/+config.ts b/apps/api/src/routes/v1/rest/websoc/+config.ts index e6a2b839..a540ff5b 100644 --- a/apps/api/src/routes/v1/rest/websoc/+config.ts +++ b/apps/api/src/routes/v1/rest/websoc/+config.ts @@ -13,8 +13,8 @@ import { constructs, esbuildOptions } from "../../../../../bronya.config"; export const overrides: ApiPropsOverride = { constructs: { ...constructs, - functionProps: (scope, id) => ({ - ...constructs.functionProps?.(scope, id), + functionProps: (scope, id, route) => ({ + ...constructs.functionProps?.(scope, id, route), role: new Role(scope, `${id}-v1-rest-websoc-role`, { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ diff --git a/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts b/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts index e411e9dd..bcc3a1f0 100644 --- a/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts +++ b/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts @@ -13,8 +13,8 @@ import { constructs, esbuildOptions } from "../../../../../../bronya.config"; export const overrides: ApiPropsOverride = { constructs: { ...constructs, - functionProps: (scope, id) => ({ - ...constructs.functionProps?.(scope, id), + functionProps: (scope, id, route) => ({ + ...constructs.functionProps?.(scope, id, route), role: new Role(scope, `${id}-v1-rest-websoc-id-role`, { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ From a98c193d59b01b1a73fc101caaa1044b4b570e67 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:18:59 -0800 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20set=20ddb=20table?= =?UTF-8?q?=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/bronya.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index 16228f6d..949c1195 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -237,6 +237,7 @@ class ApiStack extends Stack { esbuild: esbuildOptions, }); this.cache = new Table(this, `${id}-cache`, { + tableName: `${id}-cache`, partitionKey: { name: "cacheKey", type: AttributeType.STRING }, timeToLiveAttribute: "expireAt", billingMode: BillingMode.PAY_PER_REQUEST,