diff --git a/lib/lambda/processEmails.ts b/lib/lambda/processEmails.ts index 29a86325a..5ad46bec5 100644 --- a/lib/lambda/processEmails.ts +++ b/lib/lambda/processEmails.ts @@ -1,5 +1,5 @@ import { SESClient, SendEmailCommand, SendEmailCommandInput } from "@aws-sdk/client-ses"; -import { EmailAddresses, KafkaEvent, KafkaRecord } from "shared-types"; +import { EmailAddresses, KafkaEvent, KafkaRecord, Events } from "shared-types"; import { decodeBase64WithUtf8, getSecret } from "shared-utils"; import { Handler } from "aws-lambda"; import { getEmailTemplates, getAllStateUsers } from "libs/email"; @@ -8,7 +8,7 @@ import { EMAIL_CONFIG, getCpocEmail, getSrtEmails } from "libs/email/content/ema import { htmlToText, HtmlToTextOptions } from "html-to-text"; import pLimit from "p-limit"; import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; -import { getNamespace } from "libs/utils"; +import { getOsNamespace } from "libs/utils"; class TemporaryError extends Error { constructor(message: string) { @@ -139,7 +139,10 @@ export async function processRecord(kafkaRecord: KafkaRecord, config: ProcessEma console.log("Config:", JSON.stringify(config, null, 2)); await processAndSendEmails(record, id, config); } catch (error) { - console.error("Error processing record:", JSON.stringify(error, null, 2)); + console.error( + "Error processing record: { record, id, config }", + JSON.stringify({ record, id, config }, null, 2), + ); throw error; } } @@ -153,11 +156,12 @@ export function validateEmailTemplate(template: any) { } } -export async function processAndSendEmails(record: any, id: string, config: ProcessEmailConfig) { - const templates = await getEmailTemplates( - record.event, - record.authority.toLowerCase(), - ); +export async function processAndSendEmails( + record: Events[keyof Events], + id: string, + config: ProcessEmailConfig, +) { + const templates = await getEmailTemplates(record); if (!templates) { console.log( @@ -174,7 +178,7 @@ export async function processAndSendEmails(record: any, id: string, config: Proc const sec = await getSecret(config.emailAddressLookupSecretName); - const item = await os.getItem(config.osDomain, getNamespace("main"), id); + const item = await os.getItem(config.osDomain, getOsNamespace("main"), id); if (!item?.found || !item?._source) { console.log(`The package was not found for id: ${id}. Doing nothing.`); return; diff --git a/lib/lambda/processEmailsHandler.test.ts b/lib/lambda/processEmailsHandler.test.ts index 0eb98d598..17974fdba 100644 --- a/lib/lambda/processEmailsHandler.test.ts +++ b/lib/lambda/processEmailsHandler.test.ts @@ -89,18 +89,6 @@ describe("process emails Handler", () => { ncs, SIMPLE_ID, ], - [ - `should send an email for ${tempExtension} with ${Authority.MED_SPA}`, - Authority.MED_SPA, - tempExtension, - SIMPLE_ID, - ], - [ - `should send an email for ${tempExtension} with ${Authority.CHIP_SPA}`, - Authority.CHIP_SPA, - tempExtension, - SIMPLE_ID, - ], [ `should send an email for ${tempExtension} with ${Authority["1915b"]}`, Authority["1915b"], diff --git a/lib/lambda/search.ts b/lib/lambda/search.ts index 6e4a4aa8d..6b07d6621 100644 --- a/lib/lambda/search.ts +++ b/lib/lambda/search.ts @@ -13,6 +13,10 @@ export const getSearchData = async (event: APIGatewayEvent) => { validateEnvVariable("osDomain"); if (!event.pathParameters || !event.pathParameters.index) { + console.error( + "event.pathParameters.index path parameter required, Event: ", + JSON.stringify(event, null, 2), + ); return response({ statusCode: 400, body: { message: "Index path parameter required" }, diff --git a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts index 3fd5ca4a4..28e85fd31 100644 --- a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts +++ b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts @@ -2,7 +2,7 @@ import { events } from "shared-types/events"; import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; -import { getDomain, getNamespace } from "libs/utils"; +import { getDomain, getOsNamespace } from "libs/utils"; import * as os from "libs/opensearch-lib"; export const respondToRai = async (event: APIGatewayEvent) => { @@ -23,7 +23,7 @@ export const respondToRai = async (event: APIGatewayEvent) => { throw "Item Doesn't Exist"; } - const item = await os.getItem(getDomain(), getNamespace("main"), parsedResult.data.id); + const item = await os.getItem(getDomain(), getOsNamespace("main"), parsedResult.data.id); const authDetails = getAuthDetails(event); const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; diff --git a/lib/libs/api/package/itemExists.ts b/lib/libs/api/package/itemExists.ts index 0610657f7..8b1f28228 100644 --- a/lib/libs/api/package/itemExists.ts +++ b/lib/libs/api/package/itemExists.ts @@ -1,19 +1,13 @@ import * as os from "../../../libs/opensearch-lib"; -import { getDomain, getNamespace } from "libs/utils"; +import { getDomain, getOsNamespace } from "libs/utils"; import { BaseIndex } from "lib/packages/shared-types/opensearch"; -export async function itemExists(params: { - id: string; - osDomain?: string; - indexNamespace?: string; -}): Promise { +export async function itemExists({ id }: { id: string }): Promise { try { - const domain = params.osDomain || getDomain(); - const index: `${string}${BaseIndex}` = params.indexNamespace - ? `${params.indexNamespace}main` - : getNamespace("main"); + const domain = getDomain(); + const index: `${string}${BaseIndex}` = getOsNamespace("main"); - const packageResult = await os.getItem(domain, index, params.id); + const packageResult = await os.getItem(domain, index, id); return packageResult?._source !== undefined && packageResult?._source !== null; } catch (error) { console.error(error); diff --git a/lib/libs/email/content/tempExtension/index.tsx b/lib/libs/email/content/tempExtension/index.tsx index 9b34859b2..65ee650cc 100644 --- a/lib/libs/email/content/tempExtension/index.tsx +++ b/lib/libs/email/content/tempExtension/index.tsx @@ -1,26 +1,48 @@ -import { EmailAddresses, Events } from "shared-types"; +import { Authority, EmailAddresses, Events } from "shared-types"; import { CommonEmailVariables } from "shared-types"; -import { UserTypeOnlyTemplate } from "../.."; +import { AuthoritiesWithUserTypesTemplate } from "../.."; import { render } from "@react-email/render"; import { TempExtCMSEmail, TempExtStateEmail } from "./emailTemplates"; -export const tempExtention: UserTypeOnlyTemplate = { - cms: async ( - variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses }, - ) => { - return { - to: variables.emails.osgEmail, - subject: `${variables.authority} Waiver Extension ${variables.id} Submitted`, - body: await render(), - }; +export const tempExtension: AuthoritiesWithUserTypesTemplate = { + [Authority["1915b"]]: { + cms: async ( + variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses }, + ) => { + return { + to: variables.emails.osgEmail, + subject: `${variables.authority} Waiver Extension ${variables.id} Submitted`, + body: await render(), + }; + }, + state: async ( + variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses }, + ) => { + return { + to: [`${variables.submitterName} <${variables.submitterEmail}>`], + subject: `Your Request for the ${variables.authority} Waiver Extension ${variables.id} has been submitted to CMS`, + body: await render(), + }; + }, }, - state: async ( - variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses }, - ) => { - return { - to: [`${variables.submitterName} <${variables.submitterEmail}>`], - subject: `Your Request for the ${variables.authority} Waiver Extension ${variables.id} has been submitted to CMS`, - body: await render(), - }; + [Authority["1915c"]]: { + cms: async ( + variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses }, + ) => { + return { + to: variables.emails.osgEmail, + subject: `${variables.authority} Waiver Extension ${variables.id} Submitted`, + body: await render(), + }; + }, + state: async ( + variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses }, + ) => { + return { + to: [`${variables.submitterName} <${variables.submitterEmail}>`], + subject: `Your Request for the ${variables.authority} Waiver Extension ${variables.id} has been submitted to CMS`, + body: await render(), + }; + }, }, }; diff --git a/lib/libs/email/index.ts b/lib/libs/email/index.ts index 57f4d7824..e52853246 100644 --- a/lib/libs/email/index.ts +++ b/lib/libs/email/index.ts @@ -1,4 +1,4 @@ -import { Authority } from "shared-types"; +import { Authority, Events } from "shared-types"; import { getPackageChangelog } from "../api/package"; import * as EmailContent from "./content"; import { changelog } from "shared-types/opensearch"; @@ -23,7 +23,7 @@ export type AuthoritiesWithUserTypesTemplate = { export type EmailTemplates = { "new-medicaid-submission": AuthoritiesWithUserTypesTemplate; "new-chip-submission": AuthoritiesWithUserTypesTemplate; - "temporary-extension": UserTypeOnlyTemplate; + "temporary-extension": AuthoritiesWithUserTypesTemplate; "withdraw-package": AuthoritiesWithUserTypesTemplate; "withdraw-rai": AuthoritiesWithUserTypesTemplate; "contracting-initial": AuthoritiesWithUserTypesTemplate; @@ -38,13 +38,14 @@ export type EmailTemplates = { "capitated-renewal-state": AuthoritiesWithUserTypesTemplate; "respond-to-rai": AuthoritiesWithUserTypesTemplate; "app-k": AuthoritiesWithUserTypesTemplate; + "upload-subsequent-documents": AuthoritiesWithUserTypesTemplate; }; // Create a type-safe mapping of email templates const emailTemplates: EmailTemplates = { "new-medicaid-submission": EmailContent.newSubmission, "new-chip-submission": EmailContent.newSubmission, - "temporary-extension": EmailContent.tempExtention, + "temporary-extension": EmailContent.tempExtension, "withdraw-package": EmailContent.withdrawPackage, "withdraw-rai": EmailContent.withdrawRai, "contracting-initial": EmailContent.newSubmission, @@ -59,6 +60,7 @@ const emailTemplates: EmailTemplates = { "capitated-renewal-state": EmailContent.newSubmission, "respond-to-rai": EmailContent.respondToRai, "app-k": EmailContent.newSubmission, + "upload-subsequent-documents": EmailContent.newSubmission, }; // Create a type-safe lookup function @@ -70,36 +72,31 @@ export function getEmailTemplate( return emailTemplates[baseAction]; } -function isAuthorityTemplate( - obj: any, - authority: Authority, -): obj is AuthoritiesWithUserTypesTemplate { - return authority in obj; +function hasAuthority( + obj: Events[keyof Events], +): obj is Extract { + return "authority" in obj; } // Update the getEmailTemplates function to use the new lookup -export async function getEmailTemplates( - action: keyof EmailTemplates, - authority: Authority, -): Promise[] | null> { - const template = getEmailTemplate(action || "new-medicaid-submission"); - if (!template) { +export async function getEmailTemplates( + record: Events[keyof Events], +): Promise[]> { + const event = record.event; + if (!event || !(event in emailTemplates)) { console.log("No template found"); - return null; + return []; } - const emailTemplatesToSend: EmailTemplateFunction[] = []; - - if (isAuthorityTemplate(template, authority)) { - emailTemplatesToSend.push(...Object.values(template[authority] as EmailTemplateFunction)); + const template = emailTemplates[event as keyof EmailTemplates]; + if (hasAuthority(record)) { + const authorityTemplates = (template as AuthoritiesWithUserTypesTemplate)[ + record.authority.toLowerCase() as Authority + ]; + return authorityTemplates ? Object.values(authorityTemplates) : []; } else { - emailTemplatesToSend.push( - ...Object.values(template as Record>), - ); + return Object.values(template as UserTypeOnlyTemplate); } - - console.log("Email templates to send:", JSON.stringify(emailTemplatesToSend, null, 2)); - return emailTemplatesToSend; } // I think this needs to be written to handle not finding any matching events and so forth diff --git a/lib/libs/utils.ts b/lib/libs/utils.ts index fb7809d80..5c6cabc28 100644 --- a/lib/libs/utils.ts +++ b/lib/libs/utils.ts @@ -5,48 +5,43 @@ import { BaseIndex, Index } from "lib/packages/shared-types/opensearch"; * @throws if env variables are not defined, `getDomain` throws error indicating if variable is missing * @returns the value of `osDomain` */ -export function getDomain(): string; export function getDomain(): string { const domain = process.env.osDomain; - if (domain === undefined) { throw new Error("process.env.osDomain must be defined"); } - return domain; } /** - * Returns the `indexNamespace` env variables. Passing `baseIndex` appends the arg to the `index` variable - * @throws if env variables are not defined, `getNamespace` throws error indicating if variable is missing and - * the environment the application is running on `isDev` - * @returns the value of `indexNamespace` or empty string if not in development + * Returns the `indexNamespace` and `baseIndex` combined + * process.env.indexNamespace (THIS SHOULD BE THE BRANCH NAME & SHOULD ALWAYS BE DEFINED) + * @throws if process.env.indexNamespace not defined. + * @returns the value of `indexNamespace` and `baseIndex` combined */ -export function getNamespace(baseIndex?: T): Index; -export function getNamespace(baseIndex?: BaseIndex) { - const indexNamespace = process.env.indexNamespace ?? ""; - if (indexNamespace == "" && process.env.isDev == "true") { +export function getOsNamespace(baseIndex: BaseIndex): Index { + const indexNamespace = process.env.indexNamespace; + + if (!indexNamespace) { throw new Error("process.env.indexNamespace must be defined"); } - const index = `${indexNamespace}${baseIndex}`; - - return index; + return `${indexNamespace}${baseIndex}`; } /** - * Returns the `osDomain` and `indexNamespace` env variables. Passing `baseIndex` appends the arg to the `index` variable - * @throws if env variables are not defined, `getDomainAndNamespace` throws error indicating which variable is missing - * @returns + * Gets both the OpenSearch domain and namespace combined with the base index + * @param baseIndex - The base index to combine with the namespace + * @throws {Error} If required environment variables are not defined + * @returns Object containing: + * - domain: The OpenSearch domain from environment variables + * - index: The namespace and base index combined */ -export function getDomainAndNamespace( - baseIndex: T, -): { domain: string; index: Index }; export function getDomainAndNamespace(baseIndex: BaseIndex) { const domain = getDomain(); - const index = getNamespace(baseIndex); + const index = getOsNamespace(baseIndex); return { index, domain }; }