Skip to content

Commit 990abeb

Browse files
Merge pull request #1057 from Enterprise-CMCS/main
Release to val
2 parents 00430fe + 44b3931 commit 990abeb

File tree

11 files changed

+128
-107
lines changed

11 files changed

+128
-107
lines changed

lib/lambda/processEmails.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SESClient, SendEmailCommand, SendEmailCommandInput } from "@aws-sdk/client-ses";
2-
import { EmailAddresses, KafkaEvent, KafkaRecord } from "shared-types";
2+
import { EmailAddresses, KafkaEvent, KafkaRecord, Events } from "shared-types";
33
import { decodeBase64WithUtf8, getSecret } from "shared-utils";
44
import { Handler } from "aws-lambda";
55
import { getEmailTemplates, getAllStateUsers } from "libs/email";
@@ -8,7 +8,7 @@ import { EMAIL_CONFIG, getCpocEmail, getSrtEmails } from "libs/email/content/ema
88
import { htmlToText, HtmlToTextOptions } from "html-to-text";
99
import pLimit from "p-limit";
1010
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
11-
import { getNamespace } from "libs/utils";
11+
import { getOsNamespace } from "libs/utils";
1212

1313
class TemporaryError extends Error {
1414
constructor(message: string) {
@@ -139,7 +139,10 @@ export async function processRecord(kafkaRecord: KafkaRecord, config: ProcessEma
139139
console.log("Config:", JSON.stringify(config, null, 2));
140140
await processAndSendEmails(record, id, config);
141141
} catch (error) {
142-
console.error("Error processing record:", JSON.stringify(error, null, 2));
142+
console.error(
143+
"Error processing record: { record, id, config }",
144+
JSON.stringify({ record, id, config }, null, 2),
145+
);
143146
throw error;
144147
}
145148
}
@@ -153,11 +156,12 @@ export function validateEmailTemplate(template: any) {
153156
}
154157
}
155158

156-
export async function processAndSendEmails(record: any, id: string, config: ProcessEmailConfig) {
157-
const templates = await getEmailTemplates<typeof record>(
158-
record.event,
159-
record.authority.toLowerCase(),
160-
);
159+
export async function processAndSendEmails(
160+
record: Events[keyof Events],
161+
id: string,
162+
config: ProcessEmailConfig,
163+
) {
164+
const templates = await getEmailTemplates(record);
161165

162166
if (!templates) {
163167
console.log(
@@ -174,7 +178,7 @@ export async function processAndSendEmails(record: any, id: string, config: Proc
174178

175179
const sec = await getSecret(config.emailAddressLookupSecretName);
176180

177-
const item = await os.getItem(config.osDomain, getNamespace("main"), id);
181+
const item = await os.getItem(config.osDomain, getOsNamespace("main"), id);
178182
if (!item?.found || !item?._source) {
179183
console.log(`The package was not found for id: ${id}. Doing nothing.`);
180184
return;

lib/lambda/processEmailsHandler.test.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,6 @@ describe("process emails Handler", () => {
8989
ncs,
9090
SIMPLE_ID,
9191
],
92-
[
93-
`should send an email for ${tempExtension} with ${Authority.MED_SPA}`,
94-
Authority.MED_SPA,
95-
tempExtension,
96-
SIMPLE_ID,
97-
],
98-
[
99-
`should send an email for ${tempExtension} with ${Authority.CHIP_SPA}`,
100-
Authority.CHIP_SPA,
101-
tempExtension,
102-
SIMPLE_ID,
103-
],
10492
[
10593
`should send an email for ${tempExtension} with ${Authority["1915b"]}`,
10694
Authority["1915b"],

lib/lambda/search.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export const getSearchData = async (event: APIGatewayEvent) => {
1313
validateEnvVariable("osDomain");
1414

1515
if (!event.pathParameters || !event.pathParameters.index) {
16+
console.error(
17+
"event.pathParameters.index path parameter required, Event: ",
18+
JSON.stringify(event, null, 2),
19+
);
1620
return response({
1721
statusCode: 400,
1822
body: { message: "Index path parameter required" },

lib/lambda/submit/submissionPayloads/respond-to-rai.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { events } from "shared-types/events";
22
import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user";
33
import { type APIGatewayEvent } from "aws-lambda";
44
import { itemExists } from "libs/api/package";
5-
import { getDomain, getNamespace } from "libs/utils";
5+
import { getDomain, getOsNamespace } from "libs/utils";
66
import * as os from "libs/opensearch-lib";
77

88
export const respondToRai = async (event: APIGatewayEvent) => {
@@ -23,7 +23,7 @@ export const respondToRai = async (event: APIGatewayEvent) => {
2323
throw "Item Doesn't Exist";
2424
}
2525

26-
const item = await os.getItem(getDomain(), getNamespace("main"), parsedResult.data.id);
26+
const item = await os.getItem(getDomain(), getOsNamespace("main"), parsedResult.data.id);
2727
const authDetails = getAuthDetails(event);
2828
const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId);
2929
const submitterEmail = userAttr.email;

lib/libs/api/package/itemExists.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import * as os from "../../../libs/opensearch-lib";
2-
import { getDomain, getNamespace } from "libs/utils";
2+
import { getDomain, getOsNamespace } from "libs/utils";
33
import { BaseIndex } from "lib/packages/shared-types/opensearch";
44

5-
export async function itemExists(params: {
6-
id: string;
7-
osDomain?: string;
8-
indexNamespace?: string;
9-
}): Promise<boolean> {
5+
export async function itemExists({ id }: { id: string }): Promise<boolean> {
106
try {
11-
const domain = params.osDomain || getDomain();
12-
const index: `${string}${BaseIndex}` = params.indexNamespace
13-
? `${params.indexNamespace}main`
14-
: getNamespace("main");
7+
const domain = getDomain();
8+
const index: `${string}${BaseIndex}` = getOsNamespace("main");
159

16-
const packageResult = await os.getItem(domain, index, params.id);
10+
const packageResult = await os.getItem(domain, index, id);
1711
return packageResult?._source !== undefined && packageResult?._source !== null;
1812
} catch (error) {
1913
console.error(error);
Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
1-
import { EmailAddresses, Events } from "shared-types";
1+
import { Authority, EmailAddresses, Events } from "shared-types";
22
import { CommonEmailVariables } from "shared-types";
3-
import { UserTypeOnlyTemplate } from "../..";
3+
import { AuthoritiesWithUserTypesTemplate } from "../..";
44
import { render } from "@react-email/render";
55
import { TempExtCMSEmail, TempExtStateEmail } from "./emailTemplates";
66

7-
export const tempExtention: UserTypeOnlyTemplate = {
8-
cms: async (
9-
variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses },
10-
) => {
11-
return {
12-
to: variables.emails.osgEmail,
13-
subject: `${variables.authority} Waiver Extension ${variables.id} Submitted`,
14-
body: await render(<TempExtCMSEmail variables={variables} />),
15-
};
7+
export const tempExtension: AuthoritiesWithUserTypesTemplate = {
8+
[Authority["1915b"]]: {
9+
cms: async (
10+
variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses },
11+
) => {
12+
return {
13+
to: variables.emails.osgEmail,
14+
subject: `${variables.authority} Waiver Extension ${variables.id} Submitted`,
15+
body: await render(<TempExtCMSEmail variables={variables} />),
16+
};
17+
},
18+
state: async (
19+
variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses },
20+
) => {
21+
return {
22+
to: [`${variables.submitterName} <${variables.submitterEmail}>`],
23+
subject: `Your Request for the ${variables.authority} Waiver Extension ${variables.id} has been submitted to CMS`,
24+
body: await render(<TempExtStateEmail variables={variables} />),
25+
};
26+
},
1627
},
17-
state: async (
18-
variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses },
19-
) => {
20-
return {
21-
to: [`${variables.submitterName} <${variables.submitterEmail}>`],
22-
subject: `Your Request for the ${variables.authority} Waiver Extension ${variables.id} has been submitted to CMS`,
23-
body: await render(<TempExtStateEmail variables={variables} />),
24-
};
28+
[Authority["1915c"]]: {
29+
cms: async (
30+
variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses },
31+
) => {
32+
return {
33+
to: variables.emails.osgEmail,
34+
subject: `${variables.authority} Waiver Extension ${variables.id} Submitted`,
35+
body: await render(<TempExtCMSEmail variables={variables} />),
36+
};
37+
},
38+
state: async (
39+
variables: Events["TemporaryExtension"] & CommonEmailVariables & { emails: EmailAddresses },
40+
) => {
41+
return {
42+
to: [`${variables.submitterName} <${variables.submitterEmail}>`],
43+
subject: `Your Request for the ${variables.authority} Waiver Extension ${variables.id} has been submitted to CMS`,
44+
body: await render(<TempExtStateEmail variables={variables} />),
45+
};
46+
},
2547
},
2648
};

lib/libs/email/index.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Authority } from "shared-types";
1+
import { Authority, Events } from "shared-types";
22
import { getPackageChangelog } from "../api/package";
33
import * as EmailContent from "./content";
44
import { changelog } from "shared-types/opensearch";
@@ -23,7 +23,7 @@ export type AuthoritiesWithUserTypesTemplate = {
2323
export type EmailTemplates = {
2424
"new-medicaid-submission": AuthoritiesWithUserTypesTemplate;
2525
"new-chip-submission": AuthoritiesWithUserTypesTemplate;
26-
"temporary-extension": UserTypeOnlyTemplate;
26+
"temporary-extension": AuthoritiesWithUserTypesTemplate;
2727
"withdraw-package": AuthoritiesWithUserTypesTemplate;
2828
"withdraw-rai": AuthoritiesWithUserTypesTemplate;
2929
"contracting-initial": AuthoritiesWithUserTypesTemplate;
@@ -38,13 +38,14 @@ export type EmailTemplates = {
3838
"capitated-renewal-state": AuthoritiesWithUserTypesTemplate;
3939
"respond-to-rai": AuthoritiesWithUserTypesTemplate;
4040
"app-k": AuthoritiesWithUserTypesTemplate;
41+
"upload-subsequent-documents": AuthoritiesWithUserTypesTemplate;
4142
};
4243

4344
// Create a type-safe mapping of email templates
4445
const emailTemplates: EmailTemplates = {
4546
"new-medicaid-submission": EmailContent.newSubmission,
4647
"new-chip-submission": EmailContent.newSubmission,
47-
"temporary-extension": EmailContent.tempExtention,
48+
"temporary-extension": EmailContent.tempExtension,
4849
"withdraw-package": EmailContent.withdrawPackage,
4950
"withdraw-rai": EmailContent.withdrawRai,
5051
"contracting-initial": EmailContent.newSubmission,
@@ -59,6 +60,7 @@ const emailTemplates: EmailTemplates = {
5960
"capitated-renewal-state": EmailContent.newSubmission,
6061
"respond-to-rai": EmailContent.respondToRai,
6162
"app-k": EmailContent.newSubmission,
63+
"upload-subsequent-documents": EmailContent.newSubmission,
6264
};
6365

6466
// Create a type-safe lookup function
@@ -70,36 +72,31 @@ export function getEmailTemplate(
7072
return emailTemplates[baseAction];
7173
}
7274

73-
function isAuthorityTemplate(
74-
obj: any,
75-
authority: Authority,
76-
): obj is AuthoritiesWithUserTypesTemplate {
77-
return authority in obj;
75+
function hasAuthority(
76+
obj: Events[keyof Events],
77+
): obj is Extract<Events[keyof Events], { authority: string }> {
78+
return "authority" in obj;
7879
}
7980

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

91-
const emailTemplatesToSend: EmailTemplateFunction<T>[] = [];
92-
93-
if (isAuthorityTemplate(template, authority)) {
94-
emailTemplatesToSend.push(...Object.values(template[authority] as EmailTemplateFunction<T>));
91+
const template = emailTemplates[event as keyof EmailTemplates];
92+
if (hasAuthority(record)) {
93+
const authorityTemplates = (template as AuthoritiesWithUserTypesTemplate)[
94+
record.authority.toLowerCase() as Authority
95+
];
96+
return authorityTemplates ? Object.values(authorityTemplates) : [];
9597
} else {
96-
emailTemplatesToSend.push(
97-
...Object.values(template as Record<UserType, EmailTemplateFunction<T>>),
98-
);
98+
return Object.values(template as UserTypeOnlyTemplate);
9999
}
100-
101-
console.log("Email templates to send:", JSON.stringify(emailTemplatesToSend, null, 2));
102-
return emailTemplatesToSend;
103100
}
104101

105102
// I think this needs to be written to handle not finding any matching events and so forth

lib/libs/utils.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,43 @@ import { BaseIndex, Index } from "lib/packages/shared-types/opensearch";
55
* @throws if env variables are not defined, `getDomain` throws error indicating if variable is missing
66
* @returns the value of `osDomain`
77
*/
8-
export function getDomain(): string;
98
export function getDomain(): string {
109
const domain = process.env.osDomain;
11-
1210
if (domain === undefined) {
1311
throw new Error("process.env.osDomain must be defined");
1412
}
15-
1613
return domain;
1714
}
1815

1916
/**
20-
* Returns the `indexNamespace` env variables. Passing `baseIndex` appends the arg to the `index` variable
21-
* @throws if env variables are not defined, `getNamespace` throws error indicating if variable is missing and
22-
* the environment the application is running on `isDev`
23-
* @returns the value of `indexNamespace` or empty string if not in development
17+
* Returns the `indexNamespace` and `baseIndex` combined
18+
* process.env.indexNamespace (THIS SHOULD BE THE BRANCH NAME & SHOULD ALWAYS BE DEFINED)
19+
* @throws if process.env.indexNamespace not defined.
20+
* @returns the value of `indexNamespace` and `baseIndex` combined
2421
*/
25-
export function getNamespace<T extends BaseIndex>(baseIndex?: T): Index;
26-
export function getNamespace(baseIndex?: BaseIndex) {
27-
const indexNamespace = process.env.indexNamespace ?? "";
2822

29-
if (indexNamespace == "" && process.env.isDev == "true") {
23+
export function getOsNamespace(baseIndex: BaseIndex): Index {
24+
const indexNamespace = process.env.indexNamespace;
25+
26+
if (!indexNamespace) {
3027
throw new Error("process.env.indexNamespace must be defined");
3128
}
3229

33-
const index = `${indexNamespace}${baseIndex}`;
34-
35-
return index;
30+
return `${indexNamespace}${baseIndex}`;
3631
}
3732

3833
/**
39-
* Returns the `osDomain` and `indexNamespace` env variables. Passing `baseIndex` appends the arg to the `index` variable
40-
* @throws if env variables are not defined, `getDomainAndNamespace` throws error indicating which variable is missing
41-
* @returns
34+
* Gets both the OpenSearch domain and namespace combined with the base index
35+
* @param baseIndex - The base index to combine with the namespace
36+
* @throws {Error} If required environment variables are not defined
37+
* @returns Object containing:
38+
* - domain: The OpenSearch domain from environment variables
39+
* - index: The namespace and base index combined
4240
*/
43-
export function getDomainAndNamespace<T extends BaseIndex>(
44-
baseIndex: T,
45-
): { domain: string; index: Index };
4641

4742
export function getDomainAndNamespace(baseIndex: BaseIndex) {
4843
const domain = getDomain();
49-
const index = getNamespace(baseIndex);
44+
const index = getOsNamespace(baseIndex);
5045

5146
return { index, domain };
5247
}

lib/local-constructs/cloudwatch-to-s3/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe("CloudWatchToS3", () => {
4848
resources: [`${bucket.bucketArn}/*`],
4949
}),
5050
expect.objectContaining({
51-
actions: ["logs:PutLogEvents"],
51+
actions: ["logs:PutLogEvents", "logs:CreateLogGroup"],
5252
resources: [
5353
`arn:aws:logs:${cdk.Stack.of(cloudWatchToS3).region}:${
5454
cdk.Stack.of(cloudWatchToS3).account

lib/local-constructs/cloudwatch-to-s3/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class CloudWatchToS3 extends Construct {
3535

3636
firehoseRole.addToPolicy(
3737
new PolicyStatement({
38-
actions: ["logs:PutLogEvents"],
38+
actions: ["logs:PutLogEvents", "logs:CreateLogGroup"],
3939
resources: [
4040
`arn:aws:logs:${cdk.Stack.of(this).region}:${
4141
cdk.Stack.of(this).account

0 commit comments

Comments
 (0)