Skip to content

Commit

Permalink
fix(te): Refactor TEs to use authority and lots of cleanup (#1056)
Browse files Browse the repository at this point in the history
* Update with tempExt and appks

* Addd 1915b appks

* Update

* Update

* Update

* Fix TEs - tighten up types and 'namespaces'

* nothing waisted, nothing unknown
  • Loading branch information
benjaminpaige authored Jan 23, 2025
1 parent 52d31b6 commit 44b3931
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 99 deletions.
22 changes: 13 additions & 9 deletions lib/lambda/processEmails.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -153,11 +156,12 @@ export function validateEmailTemplate(template: any) {
}
}

export async function processAndSendEmails(record: any, id: string, config: ProcessEmailConfig) {
const templates = await getEmailTemplates<typeof record>(
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(
Expand All @@ -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;
Expand Down
12 changes: 0 additions & 12 deletions lib/lambda/processEmailsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
4 changes: 4 additions & 0 deletions lib/lambda/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
4 changes: 2 additions & 2 deletions lib/lambda/submit/submissionPayloads/respond-to-rai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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;
Expand Down
16 changes: 5 additions & 11 deletions lib/libs/api/package/itemExists.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
export async function itemExists({ id }: { id: string }): Promise<boolean> {
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);
Expand Down
60 changes: 41 additions & 19 deletions lib/libs/email/content/tempExtension/index.tsx
Original file line number Diff line number Diff line change
@@ -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(<TempExtCMSEmail variables={variables} />),
};
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(<TempExtCMSEmail variables={variables} />),
};
},
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(<TempExtStateEmail variables={variables} />),
};
},
},
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(<TempExtStateEmail variables={variables} />),
};
[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(<TempExtCMSEmail variables={variables} />),
};
},
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(<TempExtStateEmail variables={variables} />),
};
},
},
};
47 changes: 22 additions & 25 deletions lib/libs/email/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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<Events[keyof Events], { authority: string }> {
return "authority" in obj;
}

// Update the getEmailTemplates function to use the new lookup
export async function getEmailTemplates<T>(
action: keyof EmailTemplates,
authority: Authority,
): Promise<EmailTemplateFunction<T>[] | null> {
const template = getEmailTemplate(action || "new-medicaid-submission");
if (!template) {
export async function getEmailTemplates(
record: Events[keyof Events],
): Promise<EmailTemplateFunction<typeof record>[]> {
const event = record.event;
if (!event || !(event in emailTemplates)) {
console.log("No template found");
return null;
return [];
}

const emailTemplatesToSend: EmailTemplateFunction<T>[] = [];

if (isAuthorityTemplate(template, authority)) {
emailTemplatesToSend.push(...Object.values(template[authority] as EmailTemplateFunction<T>));
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<UserType, EmailTemplateFunction<T>>),
);
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
Expand Down
37 changes: 16 additions & 21 deletions lib/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends BaseIndex>(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<T extends BaseIndex>(
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 };
}

0 comments on commit 44b3931

Please sign in to comment.