Skip to content

Commit

Permalink
feat(medicaid submission): Medicaid Form Submission (#181)
Browse files Browse the repository at this point in the history
* initial structure

* More layout

* setup react-hook-form and zod and finish layout

* fix label issue and some fancy typescript to make it typesafe (tuples)

* add open sans font

* mostly working state

* fix uploads

* attachment stuff

* make mike happy

* yut

* yut

* make submission things happen for dashboard

* yut

* working

* routing

* crumbs

* date thing

* button fix thing

* buttons and such

* remove typo

* rm

* rm axios

* free brittney

* asdf

* meh.. populate with the state

* we should be able to clean this up

* Pass title in the payload

* error banner at bottom

* basic loading spinner

* modal by fire

* blue hyperlinks

* modals for success/failure

* language

* rm dropdown

* danke ben

* Fix other peoples issues

* title fix. padma 1

* syntax padma 2

* open in new tab, but idk how to link to a speicifc faq question... padma 4.1

* correct FAQ... padma 5

* spa ce bewteen sentences... padma 10

* add faq page as new tab link, but still dont know how to link to speciifc question... padma 11.1

* add text... padma 12

* make required attachment required... padma 13

* drop timestamp.. padma 15

* add missing word... padma 18

* multi file, removable file, capped at 80... padma 20, 25

* language

* add timestamp to zip

* cancel modal

* FAQ fixes, thanks wale/kevin.  padma 4.2 and 11.2

* fixes

* fix regex

* fixes

* uc

* remove help text

* modal updates

* allow past dates

* limit file types

* remove unused

* Auto upcase the ID field text

* Check user has access to state

* Check that the SPA ID does not already exist

* format with slashes

* Destructure per kevin

* Abstract per kevin

* Fix 29

* fix file size

* change name

---------

Co-authored-by: Mike Dial <[email protected]>
Co-authored-by: Benjamin Paige <[email protected]>
  • Loading branch information
3 people authored Nov 6, 2023
1 parent fa0ac18 commit 961fd0d
Show file tree
Hide file tree
Showing 29 changed files with 949 additions and 134 deletions.
1 change: 1 addition & 0 deletions src/packages/shared-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from "./errors";
export * from "./seatool";
export * from "./onemac";
export * from "./opensearch";
export * from "./uploads";
export * from "./actions";
42 changes: 32 additions & 10 deletions src/packages/shared-types/onemac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { z } from "zod";
import { s3ParseUrl } from "shared-utils/s3-url-parser";

const onemacAttachmentSchema = z.object({
s3Key: z.string(),
s3Key: z.string().nullish(),
filename: z.string(),
title: z.string(),
contentType: z.string(),
url: z.string().url(),
contentType: z.string().nullish(),
url: z.string().url().nullish(),
bucket: z.string().nullish(),
key: z.string().nullish(),
uploadDate: z.number().nullish(),
});

export const onemacSchema = z.object({
Expand All @@ -30,13 +33,30 @@ export const transformOnemac = (id: string) => {
id,
attachments:
data.attachments?.map((attachment) => {
const uploadDate = parseInt(attachment.s3Key.split("/")[0]);
const parsedUrl = s3ParseUrl(attachment.url);
if (!parsedUrl) return null;
const { bucket, key } = parsedUrl;
// this is a legacy onemac attachment
let bucket = "";
let key = "";
let uploadDate = 0;
if ("bucket" in attachment) {
bucket = attachment.bucket as string;
}
if ("key" in attachment) {
key = attachment.key as string;
}
if ("uploadDate" in attachment) {
uploadDate = attachment.uploadDate as number;
}
if (bucket == "") {
const parsedUrl = s3ParseUrl(attachment.url || "");
if (!parsedUrl) return null;
bucket = parsedUrl.bucket;
key = parsedUrl.key;
uploadDate = parseInt(attachment.s3Key?.split("/")[0] || "0");
}

return {
...attachment,
title: attachment.title,
filename: attachment.filename,
uploadDate,
bucket,
key,
Expand All @@ -49,8 +69,10 @@ export const transformOnemac = (id: string) => {
submissionTimestamp: response.submissionTimestamp,
attachments:
response.attachments?.map((attachment) => {
const uploadDate = parseInt(attachment.s3Key.split("/")[0]);
const parsedUrl = s3ParseUrl(attachment.url);
const uploadDate = parseInt(
attachment.s3Key?.split("/")[0] || "0"
);
const parsedUrl = s3ParseUrl(attachment.url || "");
if (!parsedUrl) return null;
const { bucket, key } = parsedUrl;

Expand Down
86 changes: 86 additions & 0 deletions src/packages/shared-types/uploads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export type FileTypeInfo = {
extension: string;
description: string;
mime: string;
};

export const FILE_TYPES: FileTypeInfo[] = [
{ extension: ".bmp", description: "Bitmap Image File", mime: "image/bmp" },
{
extension: ".csv",
description: "Comma-separated Values",
mime: "text/csv",
},
{
extension: ".doc",
description: "MS Word Document",
mime: "application/msword",
},
{
extension: ".docx",
description: "MS Word Document (xml)",
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
},
{
extension: ".gif",
description: "Graphics Interchange Format",
mime: "image/gif",
},
{
extension: ".jpeg",
description: "Joint Photographic Experts Group",
mime: "image/jpeg",
},
{
extension: ".odp",
description: "OpenDocument Presentation (OpenOffice)",
mime: "application/vnd.oasis.opendocument.presentation",
},
{
extension: ".ods",
description: "OpenDocument Spreadsheet (OpenOffice)",
mime: "application/vnd.oasis.opendocument.spreadsheet",
},
{
extension: ".odt",
description: "OpenDocument Text (OpenOffice)",
mime: "application/vnd.oasis.opendocument.text",
},
{
extension: ".png",
description: "Portable Network Graphic",
mime: "image/png",
},
{
extension: ".pdf",
description: "Portable Document Format",
mime: "application/pdf",
},
{
extension: ".ppt",
description: "MS Powerpoint File",
mime: "application/vnd.ms-powerpoint",
},
{
extension: ".pptx",
description: "MS Powerpoint File (xml)",
mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
},
{
extension: ".rtf",
description: "Rich Text Format",
mime: "application/rtf",
},
{ extension: ".tif", description: "Tagged Image Format", mime: "image/tiff" },
{ extension: ".txt", description: "Text File Format", mime: "text/plain" },
{
extension: ".xls",
description: "MS Excel File",
mime: "application/vnd.ms-excel",
},
{
extension: ".xlsx",
description: "MS Excel File (xml)",
mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
},
];
15 changes: 13 additions & 2 deletions src/services/api/handlers/getAttachmentUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ export const handler = async (event: APIGatewayEvent) => {
}

// Now we can generate the presigned url
const url = await generateLegacyPresignedS3Url(body.bucket, body.key, 60);
const url = await generatePresignedUrl(
body.bucket,
body.key,
body.filename,
60
);

return response<unknown>({
statusCode: 200,
Expand Down Expand Up @@ -112,14 +117,20 @@ async function getClient(bucket) {
}
}

async function generateLegacyPresignedS3Url(bucket, key, expirationInSeconds) {
async function generatePresignedUrl(
bucket,
key,
filename,
expirationInSeconds
) {
// Get an S3 client
const client = await getClient(bucket);

// Create a command to get the object (you can adjust this according to your use case)
const getObjectCommand = new GetObjectCommand({
Bucket: bucket,
Key: key,
ResponseContentDisposition: `filename ="${filename}"`,
});

// Generate a presigned URL
Expand Down
87 changes: 81 additions & 6 deletions src/services/api/handlers/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ const config = {
database: "SEA",
};

import { Kafka, KafkaMessage } from "kafkajs";
import { OneMacSink, transformOnemac } from "shared-types";

const kafka = new Kafka({
clientId: "submit",
brokers: process.env.brokerString.split(","),
retry: {
initialRetryTime: 300,
retries: 8,
},
ssl: {
rejectUnauthorized: false,
},
});

const producer = kafka.producer();

export const submit = async (event: APIGatewayEvent) => {
try {
const body = JSON.parse(event.body);
Expand All @@ -27,10 +44,20 @@ export const submit = async (event: APIGatewayEvent) => {
});
}

const pool = await sql.connect(config);
if (body.authority !== "medicaid spa") {
return response({
statusCode: 400,
body: {
message:
"The Mako Submissions API only supports Medicaid SPA at this time",
},
});
}

const pool = await sql.connect(config);
console.log(body);
const query = `
Insert into SEA.dbo.State_Plan (ID_Number, State_Code, Region_ID, Plan_Type, Submission_Date, Status_Date, SPW_Status_ID, Budget_Neutrality_Established_Flag)
Insert into SEA.dbo.State_Plan (ID_Number, State_Code, Region_ID, Plan_Type, Submission_Date, Status_Date, Proposed_Date, SPW_Status_ID, Budget_Neutrality_Established_Flag)
values ('${body.id}'
,'${body.state}'
,(Select Region_ID from SEA.dbo.States where State_Code = '${
Expand All @@ -41,6 +68,9 @@ export const submit = async (event: APIGatewayEvent) => {
}')
,dateadd(s, convert(int, left(${Date.now()}, 10)), cast('19700101' as datetime))
,dateadd(s, convert(int, left(${Date.now()}, 10)), cast('19700101' as datetime))
,dateadd(s, convert(int, left(${
body.proposedEffectiveDate
}, 10)), cast('19700101' as datetime))
,(Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = 'Pending')
,0)
`;
Expand All @@ -50,10 +80,29 @@ export const submit = async (event: APIGatewayEvent) => {

await pool.close();

return response({
statusCode: 200,
body: { message: "success" },
});
const message: OneMacSink = body;
const makoBody = transformOnemac(body.id).safeParse(message);
if (makoBody.success === false) {
// handle
console.log(
"MAKO Validation Error. The following record failed to parse: ",
JSON.stringify(message),
"Because of the following Reason(s): ",
makoBody.error.message
);
} else {
console.log(message);
await produceMessage(
process.env.topicName,
body.id,
JSON.stringify(message)
);

return response({
statusCode: 200,
body: { message: "success" },
});
}
} catch (error) {
console.error({ error });
return response({
Expand All @@ -63,4 +112,30 @@ export const submit = async (event: APIGatewayEvent) => {
}
};

async function produceMessage(topic, key, value) {
console.log("about to connect");
await producer.connect();
console.log("connected");

const message: KafkaMessage = {
key: key,
value: value,
partition: 0,
headers: { source: "micro" },
};
console.log(message);

try {
await producer.send({
topic,
messages: [message],
});
console.log("Message sent successfully");
} catch (error) {
console.error("Error sending message:", error);
} finally {
await producer.disconnect();
}
}

export const handler = submit;
9 changes: 5 additions & 4 deletions src/services/data/handlers/sink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,11 @@ export const onemac: Handler = async (event) => {
const id: string = decode(key);
const record = { id, ...JSON.parse(decode(value)) };
if (
record &&
record.sk === "Package" &&
record.submitterName &&
record.submitterName !== "-- --" // these records did not originate from onemac, thus we ignore them
record && // testing if we have a record
(record.origin === "micro" || // testing if this is a micro record
(record.sk === "Package" && // testing if this is a legacy onemac package record
record.submitterName &&
record.submitterName !== "-- --"))
) {
const result = transformOnemac(id).safeParse(record);
if (result.success === false) {
Expand Down
6 changes: 4 additions & 2 deletions src/services/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
"@aws-amplify/auth": "^5.4.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/open-sans": "^5.0.17",
"@heroicons/react": "^2.0.17",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.1",
"@hookform/resolvers": "^3.3.2",
"@mui/lab": "^5.0.0-alpha.136",
"@mui/material": "^5.14.1",
"@mui/styled-engine": "^5.13.2",
Expand Down Expand Up @@ -50,11 +51,12 @@
"file-saver": "^2.0.5",
"framer-motion": "^10.16.1",
"jszip": "^3.10.1",
"lucide-react": "^0.268.0",
"lucide-react": "^0.291.0",
"lz-string": "^1.5.0",
"react": "^18.2.0",
"react-day-picker": "^8.8.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.46.2",
"react-loader-spinner": "^5.3.4",
"react-router-dom": "^6.10.0",
Expand Down
4 changes: 3 additions & 1 deletion src/services/ui/src/api/getAttachmentUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { API } from "aws-amplify";
export const getAttachmentUrl = async (
id: string,
bucket: string,
key: string
key: string,
filename: string
) => {
const response = await API.post("os", "/getAttachmentUrl", {
body: {
id,
bucket,
key,
filename,
},
});
return response.url as string;
Expand Down
Loading

0 comments on commit 961fd0d

Please sign in to comment.