Skip to content

Commit 2a566c6

Browse files
authored
Performance optimizations (#39)
* use a single client per service * use node 22 * minify bundle, use node 22 * fix build * clear node cache before each test * add dep back
1 parent 89e7ff0 commit 2a566c6

26 files changed

+228
-153
lines changed

.github/workflows/deploy-dev.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Set up Node
1515
uses: actions/setup-node@v4
1616
with:
17-
node-version: 20.x
17+
node-version: 22.x
1818
- uses: actions/checkout@v4
1919
env:
2020
HUSKY: "0"
@@ -37,7 +37,7 @@ jobs:
3737
- name: Set up Node for testing
3838
uses: actions/setup-node@v4
3939
with:
40-
node-version: 20.x
40+
node-version: 22.x
4141
- uses: actions/checkout@v4
4242
env:
4343
HUSKY: "0"
@@ -80,7 +80,7 @@ jobs:
8080
- name: Set up Node
8181
uses: actions/setup-node@v4
8282
with:
83-
node-version: 20.x
83+
node-version: 22.x
8484
- uses: actions/checkout@v4
8585
env:
8686
HUSKY: "0"

.github/workflows/deploy-prod.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Set up Node
1515
uses: actions/setup-node@v4
1616
with:
17-
node-version: 20.x
17+
node-version: 22.x
1818
- uses: actions/checkout@v4
1919
env:
2020
HUSKY: "0"
@@ -37,7 +37,7 @@ jobs:
3737
- name: Set up Node for testing
3838
uses: actions/setup-node@v4
3939
with:
40-
node-version: 20.x
40+
node-version: 22.x
4141
- uses: actions/checkout@v4
4242
env:
4343
HUSKY: "0"
@@ -80,7 +80,7 @@ jobs:
8080
- name: Set up Node
8181
uses: actions/setup-node@v4
8282
with:
83-
node-version: 20.x
83+
node-version: 22.x
8484
- uses: actions/checkout@v4
8585
env:
8686
HUSKY: "0"
@@ -109,7 +109,7 @@ jobs:
109109
- name: Set up Node for testing
110110
uses: actions/setup-node@v4
111111
with:
112-
node-version: 20.x
112+
node-version: 22.x
113113
- uses: actions/checkout@v4
114114
env:
115115
HUSKY: "0"
@@ -152,7 +152,7 @@ jobs:
152152
- name: Set up Node for testing
153153
uses: actions/setup-node@v4
154154
with:
155-
node-version: 20.x
155+
node-version: 22.x
156156
- uses: actions/checkout@v4
157157
env:
158158
HUSKY: "0"

cloudformation/main.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,34 @@ Resources:
120120
Type: AWS::Serverless::Function
121121
DependsOn:
122122
- AppLogGroups
123+
Metadata:
124+
BuildMethod: esbuild
125+
BuildProperties:
126+
Format: esm
127+
Minify: true
128+
OutExtension:
129+
- .js=.mjs
130+
Target: "es2022"
131+
Sourcemap: false
132+
EntryPoints:
133+
- api/lambda.js
134+
External:
135+
- aws-sdk
136+
Banner:
137+
- js=import path from 'path';
138+
import { fileURLToPath } from 'url';
139+
import { createRequire as topLevelCreateRequire } from 'module';
140+
const require = topLevelCreateRequire(import.meta.url);
141+
const __filename = fileURLToPath(import.meta.url);
142+
const __dirname = path.dirname(__filename);
123143
Properties:
124144
Architectures: [arm64]
125145
CodeUri: ../dist
126146
AutoPublishAlias: live
127-
Runtime: nodejs20.x
147+
Runtime: nodejs22.x
128148
Description: !Sub "${ApplicationFriendlyName} API Lambda"
129149
FunctionName: !Sub ${ApplicationPrefix}-lambda
130-
Handler: api/lambda.handler
150+
Handler: lambda.handler
131151
MemorySize: 512
132152
Role: !GetAtt AppSecurityRoles.Outputs.MainFunctionRoleArn
133153
Timeout: 60

package.json

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"scripts": {
1212
"build": "yarn workspaces run build && yarn lockfile-manage",
1313
"dev": "concurrently --names 'api,ui' 'yarn workspace infra-core-api run dev' 'yarn workspace infra-core-ui run dev'",
14-
"lockfile-manage": "synp --with-workspace --source-file yarn.lock && cp package-lock.json dist/ && cp package.json dist/ && rm package-lock.json",
14+
"lockfile-manage": "synp --with-workspace --source-file yarn.lock && cp package-lock.json dist/ && cp src/api/package.json dist/ && rm package-lock.json",
1515
"prettier": "yarn workspaces run prettier && prettier --check tests/**/*.ts",
1616
"prettier:write": "yarn workspaces run prettier:write && prettier --write tests/**/*.ts",
1717
"lint": "yarn workspaces run lint",
@@ -25,35 +25,11 @@
2525
"test:e2e": "playwright test",
2626
"test:e2e-ui": "playwright test --ui"
2727
},
28-
"dependencies": {
29-
"@aws-sdk/client-dynamodb": "^3.624.0",
30-
"@aws-sdk/client-secrets-manager": "^3.624.0",
31-
"@aws-sdk/util-dynamodb": "^3.624.0",
32-
"@azure/msal-node": "^2.16.1",
33-
"@fastify/auth": "^5.0.1",
34-
"@fastify/aws-lambda": "^5.0.0",
35-
"@fastify/caching": "^9.0.1",
36-
"@fastify/cors": "^10.0.1",
37-
"@touch4it/ical-timezones": "^1.9.0",
38-
"discord.js": "^14.15.3",
39-
"dotenv": "^16.4.5",
40-
"fastify": "^5.1.0",
41-
"fastify-plugin": "^4.5.1",
42-
"ical-generator": "^7.2.0",
43-
"jsonwebtoken": "^9.0.2",
44-
"jwks-rsa": "^3.1.0",
45-
"moment": "^2.30.1",
46-
"moment-timezone": "^0.5.45",
47-
"node-cache": "^5.1.2",
48-
"pluralize": "^8.0.0",
49-
"zod": "^3.23.8",
50-
"zod-to-json-schema": "^3.23.2",
51-
"zod-validation-error": "^3.3.1"
52-
},
28+
"dependencies": {},
5329
"devDependencies": {
5430
"@eslint/compat": "^1.1.1",
5531
"@playwright/test": "^1.49.1",
56-
"@tsconfig/node20": "^20.1.4",
32+
"@tsconfig/node22": "^22.0.0",
5733
"@types/node": "^22.1.0",
5834
"@types/pluralize": "^0.0.33",
5935
"@types/react": "^18.3.3",
@@ -82,7 +58,7 @@
8258
"husky": "^9.1.4",
8359
"identity-obj-proxy": "^3.0.0",
8460
"jsdom": "^24.1.1",
85-
"node-ical": "^0.18.0",
61+
"node-ical": "^0.20.1",
8662
"postcss": "^8.4.41",
8763
"postcss-preset-mantine": "^1.17.0",
8864
"postcss-simple-vars": "^7.0.1",

src/api/functions/cache.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import {
66
import { genericConfig } from "../../common/config.js";
77
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
88

9-
const dynamoClient = new DynamoDBClient({
10-
region: genericConfig.AwsRegion,
11-
});
12-
139
export async function getItemFromCache(
10+
dynamoClient: DynamoDBClient,
1411
key: string,
1512
): Promise<null | Record<string, string | number>> {
1613
const currentTime = Math.floor(Date.now() / 1000);
@@ -37,6 +34,7 @@ export async function getItemFromCache(
3734
}
3835

3936
export async function insertItemIntoCache(
37+
dynamoClient: DynamoDBClient,
4038
key: string,
4139
value: Record<string, string | number>,
4240
expireAt: Date,

src/api/functions/discord.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { FastifyBaseLogger } from "fastify";
1515
import { DiscordEventError } from "../../common/errors/index.js";
1616
import { getSecretValue } from "../plugins/auth.js";
1717
import { genericConfig } from "../../common/config.js";
18+
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
1819

1920
// https://stackoverflow.com/a/3809435/5684541
2021
// https://calendar-buff.acmuiuc.pages.dev/calendar?id=dd7af73a-3df6-4e12-b228-0d2dac34fda7&date=2024-08-30
@@ -24,12 +25,13 @@ export type IUpdateDiscord = EventPostRequest & { id: string };
2425

2526
const urlRegex = /https:\/\/[a-z0-9\.-]+\/calendar\?id=([a-f0-9-]+)/;
2627
export const updateDiscord = async (
28+
smClient: SecretsManagerClient,
2729
event: IUpdateDiscord,
2830
isDelete: boolean = false,
2931
logger: FastifyBaseLogger,
3032
): Promise<null | GuildScheduledEventCreateOptions> => {
3133
const secretApiConfig =
32-
(await getSecretValue(genericConfig.ConfigSecretName)) || {};
34+
(await getSecretValue(smClient, genericConfig.ConfigSecretName)) || {};
3335
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
3436
let payload: GuildScheduledEventCreateOptions | null = null;
3537

src/api/functions/entraId.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,23 @@ import {
1818
EntraGroupActions,
1919
EntraInvitationResponse,
2020
} from "../../common/types/iam.js";
21+
import { FastifyInstance } from "fastify";
2122

2223
function validateGroupId(groupId: string): boolean {
2324
const groupIdPattern = /^[a-zA-Z0-9-]+$/; // Adjust the pattern as needed
2425
return groupIdPattern.test(groupId);
2526
}
2627

2728
export async function getEntraIdToken(
29+
fastify: FastifyInstance,
2830
clientId: string,
2931
scopes: string[] = ["https://graph.microsoft.com/.default"],
3032
) {
3133
const secretApiConfig =
32-
(await getSecretValue(genericConfig.ConfigSecretName)) || {};
34+
(await getSecretValue(
35+
fastify.secretsManagerClient,
36+
genericConfig.ConfigSecretName,
37+
)) || {};
3338
if (
3439
!secretApiConfig.entra_id_private_key ||
3540
!secretApiConfig.entra_id_thumbprint
@@ -42,7 +47,10 @@ export async function getEntraIdToken(
4247
secretApiConfig.entra_id_private_key as string,
4348
"base64",
4449
).toString("utf8");
45-
const cachedToken = await getItemFromCache("entra_id_access_token");
50+
const cachedToken = await getItemFromCache(
51+
fastify.dynamoClient,
52+
"entra_id_access_token",
53+
);
4654
if (cachedToken) {
4755
return cachedToken["token"] as string;
4856
}
@@ -70,6 +78,7 @@ export async function getEntraIdToken(
7078
date.setTime(date.getTime() - 30000);
7179
if (result?.accessToken) {
7280
await insertItemIntoCache(
81+
fastify.dynamoClient,
7382
"entra_id_access_token",
7483
{ token: result?.accessToken },
7584
date,

src/api/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,22 @@ import iamRoutes from "./routes/iam.js";
1919
import ticketsPlugin from "./routes/tickets.js";
2020
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
2121
import NodeCache from "node-cache";
22+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
23+
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
2224

2325
dotenv.config();
2426

2527
const now = () => Date.now();
2628

2729
async function init() {
30+
const dynamoClient = new DynamoDBClient({
31+
region: genericConfig.AwsRegion,
32+
});
33+
34+
const secretsManagerClient = new SecretsManagerClient({
35+
region: genericConfig.AwsRegion,
36+
});
37+
2838
const app: FastifyInstance = fastify({
2939
logger: {
3040
level: process.env.LOG_LEVEL || "info",
@@ -70,6 +80,8 @@ async function init() {
7080
app.environmentConfig =
7181
environmentConfig[app.runEnvironment as RunEnvironment];
7282
app.nodeCache = new NodeCache({ checkperiod: 30 });
83+
app.dynamoClient = dynamoClient;
84+
app.secretsManagerClient = secretsManagerClient;
7385
app.addHook("onRequest", (req, _, done) => {
7486
req.startTime = now();
7587
const hostname = req.hostname;
@@ -108,7 +120,7 @@ async function init() {
108120
await app.register(cors, {
109121
origin: app.environmentConfig.ValidCorsOrigins,
110122
});
111-
123+
app.log.info("Initialized new Fastify instance...");
112124
return app;
113125
}
114126

src/api/package.json

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,33 @@
1515
"prettier:write": "prettier --write *.ts **/*.ts"
1616
},
1717
"dependencies": {
18+
"@aws-sdk/client-dynamodb": "^3.624.0",
19+
"@aws-sdk/client-secrets-manager": "^3.624.0",
1820
"@aws-sdk/client-sts": "^3.726.0",
19-
"node-cache": "^5.1.2"
21+
"@aws-sdk/util-dynamodb": "^3.624.0",
22+
"@azure/msal-node": "^2.16.1",
23+
"@fastify/auth": "^5.0.1",
24+
"@fastify/aws-lambda": "^5.0.0",
25+
"@fastify/caching": "^9.0.1",
26+
"@fastify/cors": "^10.0.1",
27+
"@touch4it/ical-timezones": "^1.9.0",
28+
"discord.js": "^14.15.3",
29+
"dotenv": "^16.4.5",
30+
"esbuild": "^0.24.2",
31+
"fastify": "^5.1.0",
32+
"fastify-plugin": "^4.5.1",
33+
"ical-generator": "^7.2.0",
34+
"jsonwebtoken": "^9.0.2",
35+
"jwks-rsa": "^3.1.0",
36+
"moment": "^2.30.1",
37+
"moment-timezone": "^0.5.45",
38+
"node-cache": "^5.1.2",
39+
"pluralize": "^8.0.0",
40+
"zod": "^3.23.8",
41+
"zod-to-json-schema": "^3.23.2",
42+
"zod-validation-error": "^3.3.1"
43+
},
44+
"devDependencies": {
45+
"@tsconfig/node22": "^22.0.0"
2046
}
21-
}
47+
}

src/api/plugins/auth.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,9 @@ export type AadToken = {
5353
ver: string;
5454
roles?: string[];
5555
};
56-
const smClient = new SecretsManagerClient({
57-
region: genericConfig.AwsRegion,
58-
});
59-
60-
const dynamoClient = new DynamoDBClient({
61-
region: genericConfig.AwsRegion,
62-
});
6356

6457
export const getSecretValue = async (
58+
smClient: SecretsManagerClient,
6559
secretId: string,
6660
): Promise<Record<string, string | number | boolean> | null | SecretConfig> => {
6761
const data = await smClient.send(
@@ -118,7 +112,10 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
118112
signingKey =
119113
process.env.JwtSigningKey ||
120114
((
121-
(await getSecretValue(genericConfig.ConfigSecretName)) || {
115+
(await getSecretValue(
116+
fastify.secretsManagerClient,
117+
genericConfig.ConfigSecretName,
118+
)) || {
122119
jwt_key: "",
123120
}
124121
).jwt_key as string) ||
@@ -168,7 +165,7 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
168165
if (verifiedTokenData.groups) {
169166
const groupRoles = await Promise.allSettled(
170167
verifiedTokenData.groups.map((x) =>
171-
getGroupRoles(dynamoClient, fastify, x),
168+
getGroupRoles(fastify.dynamoClient, fastify, x),
172169
),
173170
);
174171
for (const result of groupRoles) {
@@ -201,7 +198,7 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
201198
if (request.username) {
202199
try {
203200
const userAuth = await getUserRoles(
204-
dynamoClient,
201+
fastify.dynamoClient,
205202
fastify,
206203
request.username,
207204
);

0 commit comments

Comments
 (0)