Skip to content

Commit a87ce91

Browse files
committed
use a single client per service
1 parent 89e7ff0 commit a87ce91

File tree

10 files changed

+94
-53
lines changed

10 files changed

+94
-53
lines changed

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/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
);

src/api/routes/events.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { zodToJsonSchema } from "zod-to-json-schema";
55
import { OrganizationList } from "../../common/orgs.js";
66
import {
77
DeleteItemCommand,
8-
DynamoDBClient,
98
GetItemCommand,
109
PutItemCommand,
1110
QueryCommand,
@@ -27,6 +26,7 @@ import { IUpdateDiscord, updateDiscord } from "../functions/discord.js";
2726
// POST
2827

2928
const repeatOptions = ["weekly", "biweekly"] as const;
29+
const EVENT_CACHE_SECONDS = 90;
3030
export type EventRepeatOptions = (typeof repeatOptions)[number];
3131

3232
const baseSchema = z.object({
@@ -80,10 +80,6 @@ const getEventsSchema = z.array(getEventSchema);
8080
export type EventsGetResponse = z.infer<typeof getEventsSchema>;
8181
type EventsGetQueryParams = { upcomingOnly?: boolean };
8282

83-
const dynamoClient = new DynamoDBClient({
84-
region: genericConfig.AwsRegion,
85-
});
86-
8783
const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
8884
fastify.post<{ Body: EventPostRequest }>(
8985
"/:id?",
@@ -106,7 +102,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
106102
).id;
107103
const entryUUID = userProvidedId || randomUUID();
108104
if (userProvidedId) {
109-
const response = await dynamoClient.send(
105+
const response = await fastify.dynamoClient.send(
110106
new GetItemCommand({
111107
TableName: genericConfig.EventsDynamoTableName,
112108
Key: { id: { S: userProvidedId } },
@@ -128,7 +124,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
128124
: new Date().toISOString(),
129125
updatedAt: new Date().toISOString(),
130126
};
131-
await dynamoClient.send(
127+
await fastify.dynamoClient.send(
132128
new PutItemCommand({
133129
TableName: genericConfig.EventsDynamoTableName,
134130
Item: marshall(entry),
@@ -140,18 +136,23 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
140136
}
141137
try {
142138
if (request.body.featured && !request.body.repeats) {
143-
await updateDiscord(entry, false, request.log);
139+
await updateDiscord(
140+
fastify.secretsManagerClient,
141+
entry,
142+
false,
143+
request.log,
144+
);
144145
}
145146
} catch (e: unknown) {
146147
// restore original DB status if Discord fails.
147-
await dynamoClient.send(
148+
await fastify.dynamoClient.send(
148149
new DeleteItemCommand({
149150
TableName: genericConfig.EventsDynamoTableName,
150151
Key: { id: { S: entryUUID } },
151152
}),
152153
);
153154
if (userProvidedId) {
154-
await dynamoClient.send(
155+
await fastify.dynamoClient.send(
155156
new PutItemCommand({
156157
TableName: genericConfig.EventsDynamoTableName,
157158
Item: originalEvent,
@@ -198,7 +199,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
198199
async (request: FastifyRequest<EventGetRequest>, reply) => {
199200
const id = request.params.id;
200201
try {
201-
const response = await dynamoClient.send(
202+
const response = await fastify.dynamoClient.send(
202203
new QueryCommand({
203204
TableName: genericConfig.EventsDynamoTableName,
204205
KeyConditionExpression: "#id = :id",
@@ -241,13 +242,18 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
241242
async (request: FastifyRequest<EventDeleteRequest>, reply) => {
242243
const id = request.params.id;
243244
try {
244-
await dynamoClient.send(
245+
await fastify.dynamoClient.send(
245246
new DeleteItemCommand({
246247
TableName: genericConfig.EventsDynamoTableName,
247248
Key: marshall({ id }),
248249
}),
249250
);
250-
await updateDiscord({ id } as IUpdateDiscord, true, request.log);
251+
await updateDiscord(
252+
fastify.secretsManagerClient,
253+
{ id } as IUpdateDiscord,
254+
true,
255+
request.log,
256+
);
251257
reply.send({
252258
id,
253259
resource: `/api/v1/events/${id}`,
@@ -285,8 +291,20 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
285291
},
286292
async (request: FastifyRequest<EventsGetRequest>, reply) => {
287293
const upcomingOnly = request.query?.upcomingOnly || false;
294+
const cachedResponse = fastify.nodeCache.get(
295+
`events-upcoming_only=${upcomingOnly}`,
296+
);
297+
if (cachedResponse) {
298+
reply
299+
.header(
300+
"cache-control",
301+
"public, max-age=7200, stale-while-revalidate=900, stale-if-error=86400",
302+
)
303+
.header("acm-cache-status", "hit")
304+
.send(cachedResponse);
305+
}
288306
try {
289-
const response = await dynamoClient.send(
307+
const response = await fastify.dynamoClient.send(
290308
new ScanCommand({ TableName: genericConfig.EventsDynamoTableName }),
291309
);
292310
const items = response.Items?.map((item) => unmarshall(item));
@@ -322,11 +340,17 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
322340
}
323341
});
324342
}
343+
fastify.nodeCache.set(
344+
`events-upcoming_only=${upcomingOnly}`,
345+
parsedItems,
346+
EVENT_CACHE_SECONDS,
347+
);
325348
reply
326349
.header(
327350
"cache-control",
328351
"public, max-age=7200, stale-while-revalidate=900, stale-if-error=86400",
329352
)
353+
.header("acm-cache-status", "miss")
330354
.send(parsedItems);
331355
} catch (e: unknown) {
332356
if (e instanceof Error) {

src/api/routes/iam.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ import {
3939
getGroupRoles,
4040
} from "../functions/authorization.js";
4141

42-
const dynamoClient = new DynamoDBClient({
43-
region: genericConfig.AwsRegion,
44-
});
45-
4642
const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
4743
fastify.get<{
4844
Body: undefined;
@@ -67,7 +63,11 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
6763
async (request, reply) => {
6864
try {
6965
const groupId = (request.params as Record<string, string>).groupId;
70-
const roles = await getGroupRoles(dynamoClient, fastify, groupId);
66+
const roles = await getGroupRoles(
67+
fastify.dynamoClient,
68+
fastify,
69+
groupId,
70+
);
7171
return reply.send(roles);
7272
} catch (e: unknown) {
7373
if (e instanceof BaseError) {
@@ -120,7 +120,7 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
120120
createdAt: timestamp,
121121
}),
122122
});
123-
await dynamoClient.send(command);
123+
await fastify.dynamoClient.send(command);
124124
fastify.nodeCache.set(
125125
`grouproles-${groupId}`,
126126
request.body.roles,
@@ -160,6 +160,7 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
160160
async (request, reply) => {
161161
const emails = request.body.emails;
162162
const entraIdToken = await getEntraIdToken(
163+
fastify,
163164
fastify.environmentConfig.AadValidClientId,
164165
);
165166
if (!entraIdToken) {
@@ -246,6 +247,7 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
246247
});
247248
}
248249
const entraIdToken = await getEntraIdToken(
250+
fastify,
249251
fastify.environmentConfig.AadValidClientId,
250252
);
251253
const addResults = await Promise.allSettled(
@@ -369,6 +371,7 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
369371
});
370372
}
371373
const entraIdToken = await getEntraIdToken(
374+
fastify,
372375
fastify.environmentConfig.AadValidClientId,
373376
);
374377
const response = await listGroupMembers(entraIdToken, groupId);

0 commit comments

Comments
 (0)