diff --git a/app/api/id/[form]/submission/confirm/route.ts b/app/api/id/[form]/submission/confirm/route.ts index 0f5479b508..59110f17c5 100644 --- a/app/api/id/[form]/submission/confirm/route.ts +++ b/app/api/id/[form]/submission/confirm/route.ts @@ -2,21 +2,16 @@ import { NextResponse } from "next/server"; import { logMessage } from "@lib/logger"; import { middleware, jsonValidator, sessionExists } from "@lib/middleware"; import uuidArraySchema from "@lib/middleware/schemas/uuid-array.schema.json"; -import { - DynamoDBDocumentClient, - BatchGetCommand, - TransactWriteCommand, -} from "@aws-sdk/lib-dynamodb"; +import { BatchGetCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb"; import { MiddlewareProps, WithRequired } from "@lib/types"; -import { connectToDynamo } from "@lib/integration/dynamodbConnector"; +import { dynamodbClient } from "@lib/integration/dynamodbConnector"; import { AccessControlError, createAbility } from "@lib/privileges"; import { checkUserHasTemplateOwnership } from "@lib/templates"; import { logEvent } from "@lib/auditLogs"; async function getSubmissionsFromConfirmationCodes( formId: string, - confirmationCodes: string[], - dynamoDbClient: DynamoDBDocumentClient + confirmationCodes: string[] ): Promise<{ submissionsToConfirm: { name: string; confirmationCode: string }[]; confirmationCodesAlreadyUsed: string[]; @@ -44,7 +39,7 @@ async function getSubmissionsFromConfirmationCodes( }); // eslint-disable-next-line no-await-in-loop - const response = await dynamoDbClient.send(request); + const response = await dynamodbClient.send(request); if (response.Responses?.Vault) { response.Responses.Vault.forEach((record) => { @@ -91,8 +86,7 @@ async function getSubmissionsFromConfirmationCodes( async function confirm( formId: string, - submissionsToConfirm: { name: string; confirmationCode: string }[], - dynamoDbClient: DynamoDBDocumentClient + submissionsToConfirm: { name: string; confirmationCode: string }[] ): Promise { const confirmationTimestamp = Date.now(); const removalDate = confirmationTimestamp + 2592000000; // 2592000000 milliseconds = 30 days @@ -136,7 +130,7 @@ async function confirm( }), }); - await dynamoDbClient.send(request); + await dynamodbClient.send(request); } export const PUT = middleware( @@ -184,22 +178,15 @@ export const PUT = middleware( } try { - const dynamoDbClient = connectToDynamo(); - // max 100 confirmation codes per request const submissionsFromConfirmationCodes = await getSubmissionsFromConfirmationCodes( formId, - confirmationCodes, - dynamoDbClient + confirmationCodes ); if (submissionsFromConfirmationCodes.submissionsToConfirm.length > 0) { // max 50 submissions per request - await confirm( - formId, - submissionsFromConfirmationCodes.submissionsToConfirm, - dynamoDbClient - ); + await confirm(formId, submissionsFromConfirmationCodes.submissionsToConfirm); // Done asychronously to not block response back to client submissionsFromConfirmationCodes.submissionsToConfirm.forEach((confirmation) => logEvent( diff --git a/app/api/id/[form]/submission/report/route.ts b/app/api/id/[form]/submission/report/route.ts index 1d2c571ccb..09fa577635 100644 --- a/app/api/id/[form]/submission/report/route.ts +++ b/app/api/id/[form]/submission/report/route.ts @@ -3,13 +3,9 @@ import { logMessage } from "@lib/logger"; import { middleware, jsonValidator, sessionExists } from "@lib/middleware"; import { createTicket } from "@lib/integration/freshdesk"; import downloadReportProblemSchema from "@lib/middleware/schemas/download-report-problem-schema.json"; -import { - BatchGetCommand, - DynamoDBDocumentClient, - TransactWriteCommand, -} from "@aws-sdk/lib-dynamodb"; +import { BatchGetCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb"; import { MiddlewareProps, VaultStatus, WithRequired } from "@lib/types"; -import { connectToDynamo } from "@lib/integration/dynamodbConnector"; +import { dynamodbClient } from "@lib/integration/dynamodbConnector"; import { createAbility, AccessControlError } from "@lib/privileges"; import { checkUserHasTemplateOwnership } from "@lib/templates"; import { logEvent } from "@lib/auditLogs"; @@ -18,8 +14,7 @@ const MAXIMUM_SUBMISSION_NAMES_PER_REQUEST = 20; async function getSubmissionsFromSubmissionNames( formId: string, - submissionNames: string[], - dynamoDbClient: DynamoDBDocumentClient + submissionNames: string[] ): Promise<{ submissionsToReport: { name: string; confirmationCode: string }[]; submissionNamesAlreadyUsed: string[]; @@ -48,7 +43,7 @@ async function getSubmissionsFromSubmissionNames( }); // eslint-disable-next-line no-await-in-loop - const response = await dynamoDbClient.send(request); + const response = await dynamodbClient.send(request); if (response.Responses?.Vault) { response.Responses.Vault.forEach((record) => { @@ -95,8 +90,7 @@ async function getSubmissionsFromSubmissionNames( async function report( formId: string, - submissionsToReport: { name: string; confirmationCode: string }[], - dynamoDbClient: DynamoDBDocumentClient + submissionsToReport: { name: string; confirmationCode: string }[] ): Promise { const request = new TransactWriteCommand({ TransactItems: submissionsToReport.flatMap((submission) => { @@ -133,7 +127,7 @@ async function report( }), }); - await dynamoDbClient.send(request); + await dynamodbClient.send(request); } async function notifySupport( @@ -229,16 +223,13 @@ export const PUT = middleware( } try { - const dynamoDbClient = connectToDynamo(); - const submissionsFromSubmissionNames = await getSubmissionsFromSubmissionNames( formId, - entries, - dynamoDbClient + entries ); if (submissionsFromSubmissionNames.submissionsToReport.length > 0) { - await report(formId, submissionsFromSubmissionNames.submissionsToReport, dynamoDbClient); + await report(formId, submissionsFromSubmissionNames.submissionsToReport); // Note: may throw an error and handled in below catch e.g. if api key missing await notifySupport( formId, diff --git a/app/api/notify-callback/route.ts b/app/api/notify-callback/route.ts index 55a86d8448..25357c116a 100644 --- a/app/api/notify-callback/route.ts +++ b/app/api/notify-callback/route.ts @@ -4,7 +4,7 @@ import { SQSClient, GetQueueUrlCommand, SendMessageCommand } from "@aws-sdk/clie import { middleware } from "@lib/middleware"; import { MiddlewareRequest, MiddlewareReturn } from "@lib/types"; import { UpdateCommand } from "@aws-sdk/lib-dynamodb"; -import { connectToDynamo } from "@lib/integration/dynamodbConnector"; +import { dynamodbClient } from "@lib/integration/dynamodbConnector"; import { headers } from "next/headers"; const SQS_REPROCESS_SUBMISSION_QUEUE_NAME = "reprocess_submission_queue.fifo"; @@ -28,8 +28,6 @@ const getQueueURL = async () => { * @returns void */ async function removeProcessedMark(submissionID: string) { - const documentClient = connectToDynamo(); - const updateItem = { TableName: "ReliabilityQueue", Key: { @@ -42,7 +40,7 @@ async function removeProcessedMark(submissionID: string) { ReturnValues: "NONE" as const, }; - return documentClient.send(new UpdateCommand(updateItem)); + return dynamodbClient.send(new UpdateCommand(updateItem)); } /** diff --git a/lib/integration/dynamodbConnector.ts b/lib/integration/dynamodbConnector.ts index eb204c70a4..d28ed58192 100644 --- a/lib/integration/dynamodbConnector.ts +++ b/lib/integration/dynamodbConnector.ts @@ -1,15 +1,11 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; -/** - * Helper function to instantiate DynamoDB and Document client. - * https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-document-client.html - */ -export function connectToDynamo(): DynamoDBDocumentClient { - return DynamoDBDocumentClient.from( - new DynamoDBClient({ - region: process.env.AWS_REGION ?? "ca-central-1", - ...(process.env.LOCAL_AWS_ENDPOINT && { endpoint: process.env.LOCAL_AWS_ENDPOINT }), - }) - ); -} +const dynamodbClient = DynamoDBDocumentClient.from( + new DynamoDBClient({ + region: process.env.AWS_REGION ?? "ca-central-1", + ...(process.env.LOCAL_AWS_ENDPOINT && { endpoint: process.env.LOCAL_AWS_ENDPOINT }), + }) +); + +export { dynamodbClient }; diff --git a/lib/vault.ts b/lib/vault.ts index 324b1eff2f..62ce49a1f7 100644 --- a/lib/vault.ts +++ b/lib/vault.ts @@ -12,7 +12,7 @@ import { unprocessedSubmissionsCacheCheck, unprocessedSubmissionsCachePut, } from "./cache/unprocessedSubmissionsCache"; -import { connectToDynamo } from "./integration/dynamodbConnector"; +import { dynamodbClient } from "./integration/dynamodbConnector"; import { logMessage } from "./logger"; import { AccessControlError, checkPrivileges } from "./privileges"; import { chunkArray } from "@lib/utils"; @@ -86,8 +86,6 @@ const submissionTypeExists = async (ability: UserAbility, formID: string, status ); throw e; }); - const documentClient = connectToDynamo(); - const getItemsDbParams: QueryCommandInput = { TableName: "Vault", IndexName: "Status", @@ -109,7 +107,7 @@ const submissionTypeExists = async (ability: UserAbility, formID: string, status }; const queryCommand = new QueryCommand(getItemsDbParams); // eslint-disable-next-line no-await-in-loop - const response = await documentClient.send(queryCommand); + const response = await dynamodbClient.send(queryCommand); return Boolean(response.Items?.length); }; @@ -150,8 +148,6 @@ export async function listAllSubmissions( // We're going to request one more than the limit so we can consistently determine if there are more responses const responseRetrievalLimit = responseDownloadLimit + 1; - const documentClient = connectToDynamo(); - let accumulatedResponses: VaultSubmissionList[] = []; let submissionsRemaining = false; let paginationLastEvaluatedKey = null; @@ -179,7 +175,7 @@ export async function listAllSubmissions( }; const queryCommand = new QueryCommand(getItemsDbParams); // eslint-disable-next-line no-await-in-loop - const response = await documentClient.send(queryCommand); + const response = await dynamodbClient.send(queryCommand); if (response.Items?.length) { accumulatedResponses = accumulatedResponses.concat( @@ -278,7 +274,6 @@ export async function retrieveSubmissions( // DynamoDB BatchGetItem can only retrieve 100 items at a time // Create function that will run the batch in parallel const dbQuery = async (keys: Record[]) => { - const documentClient = connectToDynamo(); let accumulatedResponses: VaultSubmission[] = []; while (keys && keys.length > 0) { const queryCommand = new BatchGetCommand({ @@ -296,7 +291,7 @@ export async function retrieveSubmissions( }); // eslint-disable-next-line no-await-in-loop - const response = await documentClient.send(queryCommand); + const response = await dynamodbClient.send(queryCommand); if (response.Responses?.Vault.length) { accumulatedResponses = accumulatedResponses.concat( @@ -370,8 +365,6 @@ export async function updateLastDownloadedBy( formID: string, email: string ) { - const documentClient = connectToDynamo(); - // TransactWriteItem can only update 100 items at a time const asyncUpdateRequests = chunkArray(responses, 100).map((chunkedResponses) => { const request = new TransactWriteCommand({ @@ -402,7 +395,7 @@ export async function updateLastDownloadedBy( }), }); - return () => documentClient.send(request); + return () => dynamodbClient.send(request); }); // @todo - handle errors that can result from failing TransactWriteCommand operations @@ -444,8 +437,6 @@ export async function unprocessedSubmissions( */ export async function deleteDraftFormResponses(ability: UserAbility, formID: string) { try { - const documentClient = connectToDynamo(); - // Ensure users are owners of the form await checkAbilityToAccessSubmissions(ability, formID).catch((error) => { if (error instanceof AccessControlError) { @@ -491,7 +482,7 @@ export async function deleteDraftFormResponses(ability: UserAbility, formID: str }; const queryCommand = new QueryCommand(getItemsDbParams); // eslint-disable-next-line no-await-in-loop - const response = await documentClient.send(queryCommand); + const response = await dynamodbClient.send(queryCommand); if (response.Items?.length) { accumulatedResponses = accumulatedResponses.concat( @@ -509,7 +500,7 @@ export async function deleteDraftFormResponses(ability: UserAbility, formID: str const asyncDeleteRequests = chunkArray(accumulatedResponses, 25).map((request) => { return () => - documentClient.send( + dynamodbClient.send( new BatchWriteCommand({ RequestItems: { Vault: request.map((entryName) => ({