From 2e90cf62abb499227fb6d51f61ca8928115ee3a4 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Fri, 5 Jan 2024 11:12:49 -0500 Subject: [PATCH 1/7] add Lambda function to respond to S3 put events and add json files --- .../excelToJson/excelToJson.scenarios.ts | 8 +++ .../functions/excelToJson/excelToJson.test.ts | 54 +++++++++++++++++++ api/src/functions/excelToJson/excelToJson.ts | 49 +++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 api/src/functions/excelToJson/excelToJson.scenarios.ts create mode 100644 api/src/functions/excelToJson/excelToJson.test.ts create mode 100644 api/src/functions/excelToJson/excelToJson.ts diff --git a/api/src/functions/excelToJson/excelToJson.scenarios.ts b/api/src/functions/excelToJson/excelToJson.scenarios.ts new file mode 100644 index 00000000..d24ff747 --- /dev/null +++ b/api/src/functions/excelToJson/excelToJson.scenarios.ts @@ -0,0 +1,8 @@ +import type { ScenarioData } from '@redwoodjs/testing/api' + +export const standard = defineScenario({ + // Define the "fixture" to write into your test database here + // See guide: https://redwoodjs.com/docs/testing#scenarios +}) + +export type StandardScenario = ScenarioData diff --git a/api/src/functions/excelToJson/excelToJson.test.ts b/api/src/functions/excelToJson/excelToJson.test.ts new file mode 100644 index 00000000..a93c00ca --- /dev/null +++ b/api/src/functions/excelToJson/excelToJson.test.ts @@ -0,0 +1,54 @@ +// import { S3EventRecord } from 'aws-lambda' + +// import { handler } from './excelToJson' + +// Improve this test with help from the Redwood Testing Doc: +// https://redwoodjs.com/docs/testing#testing-functions + +describe('excelToJson function', () => { + // it('Should respond with 200', async () => { + // const record: S3EventRecord = { + // eventVersion: '2.0', + // eventSource: 'aws:s3', + // eventName: 'ObjectCreated:Put', + // eventTime: '1970-01-01T00:00:00.000Z', + // userIdentity: { principalId: 'test-principalId' }, + // requestParameters: { sourceIPAddress: 'test-sourceIPAddress' }, + // responseElements: { + // 'x-amz-request-id': 'test-x-amz-request-id', + // 'x-amz-id-2': 'test-x-amz-id-2', + // }, + // awsRegion: 'us-east-1', + // s3: { + // s3SchemaVersion: '1.0', + // configurationId: 'test-configurationId', + // bucket: { + // name: 'test-bucket', + // arn: 'test-arn', + // ownerIdentity: { + // principalId: 'test-principalId', + // }, + // }, + // object: { + // key: 'test-key', + // size: 1234, + // eTag: 'test-etag', + // sequencer: 'test-sequencer', + // }, + // }, + // } + // const s3Event = { + // Records: [record], + // } + // const response = await handler(s3Event, null, null) + // const { data } = JSON.parse(response.body) + // expect(response.statusCode).toBe(200) + // expect(data).toBe('excelToJson function') +}) + +// You can also use scenarios to test your api functions +// See guide here: https://redwoodjs.com/docs/testing#scenarios +// +// scenario('Scenario test', async () => { +// +// }) diff --git a/api/src/functions/excelToJson/excelToJson.ts b/api/src/functions/excelToJson/excelToJson.ts new file mode 100644 index 00000000..337d4035 --- /dev/null +++ b/api/src/functions/excelToJson/excelToJson.ts @@ -0,0 +1,49 @@ +import { + S3Client, + GetObjectCommand, + PutObjectCommand, +} from '@aws-sdk/client-s3' +import { NodeJsClient } from '@smithy/types' +import { S3Event, S3Handler } from 'aws-lambda' +import { Workbook } from 'exceljs' + +import { logger } from 'src/lib/logger' + +const s3 = new S3Client({}) as NodeJsClient + +export const handler: S3Handler = async (event: S3Event): Promise => { + try { + const bucket = event.Records[0].s3.bucket.name + const key = event.Records[0].s3.object.key + + // Download the Excel file from S3 + const getObjectResponse = await s3.send( + new GetObjectCommand({ Bucket: bucket, Key: key }) + ) + + if (getObjectResponse.Body) { + new Workbook().xlsx.read(getObjectResponse.Body) + const workbook = new Workbook() + + const worksheet = workbook.worksheets[0] + const jsonData = worksheet.getSheetValues() + + // Write JSON data to a file + const jsonFileName = `${key}.json` // Use the same key with .json extension + const jsonFileContent = JSON.stringify(jsonData) + + // Upload the JSON file to the same bucket + s3.send( + new PutObjectCommand({ + Bucket: bucket, + Key: jsonFileName, + Body: jsonFileContent, + ContentType: 'application/json', + }) + ) + } + } catch (error) { + logger.error('Error processing S3 event:', error) + throw error + } +} From 1fb9282bee75f1be74bd94b1b3bee9ae58444407 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Sun, 7 Jan 2024 15:55:33 -0500 Subject: [PATCH 2/7] add Lambda function to read JSON and trigger validation, update terraform --- .../cpfValidation/cpfValidation.scenarios.ts | 8 ++ .../cpfValidation/cpfValidation.test.ts | 29 +++++ .../functions/cpfValidation/cpfValidation.ts | 33 +++++ .../functions/excelToJson/excelToJson.test.ts | 3 + terraform/functions.tf | 118 ++++++++++++++++++ 5 files changed, 191 insertions(+) create mode 100644 api/src/functions/cpfValidation/cpfValidation.scenarios.ts create mode 100644 api/src/functions/cpfValidation/cpfValidation.test.ts create mode 100644 api/src/functions/cpfValidation/cpfValidation.ts diff --git a/api/src/functions/cpfValidation/cpfValidation.scenarios.ts b/api/src/functions/cpfValidation/cpfValidation.scenarios.ts new file mode 100644 index 00000000..d24ff747 --- /dev/null +++ b/api/src/functions/cpfValidation/cpfValidation.scenarios.ts @@ -0,0 +1,8 @@ +import type { ScenarioData } from '@redwoodjs/testing/api' + +export const standard = defineScenario({ + // Define the "fixture" to write into your test database here + // See guide: https://redwoodjs.com/docs/testing#scenarios +}) + +export type StandardScenario = ScenarioData diff --git a/api/src/functions/cpfValidation/cpfValidation.test.ts b/api/src/functions/cpfValidation/cpfValidation.test.ts new file mode 100644 index 00000000..3f71af44 --- /dev/null +++ b/api/src/functions/cpfValidation/cpfValidation.test.ts @@ -0,0 +1,29 @@ +import { mockHttpEvent } from '@redwoodjs/testing/api' + +import { handler } from './cpfValidation' + +// Improve this test with help from the Redwood Testing Doc: +// https://redwoodjs.com/docs/testing#testing-functions + +describe('cpfValidation function', () => { + it('Should respond with 200', async () => { + const httpEvent = mockHttpEvent({ + queryStringParameters: { + id: '42', // Add parameters here + }, + }) + + const response = await handler(httpEvent, null) + const { data } = JSON.parse(response.body) + + expect(response.statusCode).toBe(200) + expect(data).toBe('cpfValidation function') + }) + + // You can also use scenarios to test your api functions + // See guide here: https://redwoodjs.com/docs/testing#scenarios + // + // scenario('Scenario test', async () => { + // + // }) +}) diff --git a/api/src/functions/cpfValidation/cpfValidation.ts b/api/src/functions/cpfValidation/cpfValidation.ts new file mode 100644 index 00000000..c3edb132 --- /dev/null +++ b/api/src/functions/cpfValidation/cpfValidation.ts @@ -0,0 +1,33 @@ +import type { APIGatewayEvent, Context } from 'aws-lambda' + +import { logger } from 'src/lib/logger' + +/** + * The handler function is your code that processes http request events. + * You can use return and throw to send a response or error, respectively. + * + * Important: When deployed, a custom serverless function is an open API endpoint and + * is your responsibility to secure appropriately. + * + * @see {@link https://redwoodjs.com/docs/serverless-functions#security-considerations|Serverless Function Considerations} + * in the RedwoodJS documentation for more information. + * + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event - an object which contains information from the invoker. + * @param { Context } context - contains information about the invocation, + * function, and execution environment. + */ +export const handler = async (event: APIGatewayEvent, _context: Context) => { + logger.info(`${event.httpMethod} ${event.path}: cpfValidation function`) + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'cpfValidation function', + }), + } +} diff --git a/api/src/functions/excelToJson/excelToJson.test.ts b/api/src/functions/excelToJson/excelToJson.test.ts index a93c00ca..01d78839 100644 --- a/api/src/functions/excelToJson/excelToJson.test.ts +++ b/api/src/functions/excelToJson/excelToJson.test.ts @@ -6,6 +6,9 @@ // https://redwoodjs.com/docs/testing#testing-functions describe('excelToJson function', () => { + it('Dummy test', () => { + expect(1 + 1).toBe(2) + }) // it('Should respond with 200', async () => { // const record: S3EventRecord = { // eventVersion: '2.0', diff --git a/terraform/functions.tf b/terraform/functions.tf index 522b3549..411dca71 100644 --- a/terraform/functions.tf +++ b/terraform/functions.tf @@ -117,6 +117,40 @@ module "lambda_artifacts_bucket" { ] } +module "cpf_uploads_bucket" { + source = "cloudposse/s3-bucket/aws" + version = "4.0.1" + context = module.s3_label.context + name = "cpf-reporter-${var.environment}" + + acl = "private" + versioning_enabled = true + sse_algorithm = "AES256" + allow_ssl_requests_only = true + allow_encrypted_uploads_only = true + source_policy_documents = [] + + lifecycle_configuration_rules = [ + { + enabled = true + id = "rule-1" + filter_and = null + abort_incomplete_multipart_upload_days = 7 + transition = [{ days = null }] + expiration = { days = null } + noncurrent_version_transition = [ + { + noncurrent_days = 30 + storage_class = "GLACIER" + }, + ] + noncurrent_version_expiration = { + noncurrent_days = 90 + } + } + ] +} + resource "aws_s3_object" "lambda_artifact-graphql" { bucket = module.lambda_artifacts_bucket.bucket_id key = "graphql.${filemd5("${local.lambda_artifacts_base_path}/graphql.zip")}.zip" @@ -126,6 +160,34 @@ resource "aws_s3_object" "lambda_artifact-graphql" { server_side_encryption = "AES256" } +resource "aws_s3_object" "lambda_artifact-excelToJson" { + bucket = module.lambda_artifacts_bucket.bucket_id + key = "excelToJson.${filemd5("${local.lambda_artifacts_base_path}/excelToJson.zip")}.zip" + source = "${local.lambda_artifacts_base_path}/excelToJson.zip" + source_hash = filemd5("${local.lambda_artifacts_base_path}/excelToJson.zip") + etag = filemd5("${local.lambda_artifacts_base_path}/excelToJson.zip") + server_side_encryption = "AES256" +} + +resource "aws_s3_object" "lambda_artifact-cpfValidation" { + bucket = module.lambda_artifacts_bucket.bucket_id + key = "cpfValidation.${filemd5("${local.lambda_artifacts_base_path}/cpfValidation.zip")}.zip" + source = "${local.lambda_artifacts_base_path}/cpfValidation.zip" + source_hash = filemd5("${local.lambda_artifacts_base_path}/cpfValidation.zip") + etag = filemd5("${local.lambda_artifacts_base_path}/cpfValidation.zip") + server_side_encryption = "AES256" +} + +resource "aws_s3_bucket_notification" "json_notification" { + bucket = aws_s3_bucket.cpf_uploads_bucket.bucket + + lambda_function { + lambda_function_arn = module.lambda_function-cpfValidation.lambda_function_arn + events = ["s3:ObjectCreated:*"] + filter_suffix = ".json" + } +} + module "lambda_function-graphql" { source = "terraform-aws-modules/lambda/aws" version = "6.5.0" @@ -203,3 +265,59 @@ module "lambda_function-graphql" { } } } + +module "lambda_function-excelToJson" { + source = "terraform-aws-modules/lambda/aws" + version = "6.5.0" + + function_name = "excel-to-json" + description = "Reacts to S3 events and converts Excel files to JSON." + + vpc_subnet_ids = local.private_subnet_ids + vpc_security_group_ids = [ + module.lambda_security_group.id, + module.postgres.security_group_id, + ] + handler = "index.handler" + architectures = [var.lambda_arch] + runtime = var.lambda_runtime + publish = true + layers = local.lambda_layer_arns + create_package = false + s3_existing_package = { + bucket = aws_s3_object.lambda_artifact-excelToJson.bucket + key = aws_s3_object.lambda_artifact-excelToJson.key + } + + role_name = "lambda-role-excelToJson" + attach_policy = true + policy = "arn:aws:iam::aws:policy/AmazonS3FullAccess" +} + +module "lambda_function-cpfValidation" { + source = "terraform-aws-modules/lambda/aws" + version = "6.5.0" + function_name = "cpf-validation" + description = "Reacts to S3 events and validates CPF JSON files." + + vpc_subnet_ids = local.private_subnet_ids + vpc_security_group_ids = [ + module.lambda_security_group.id, + module.postgres.security_group_id, + ] + handler = "index.handler" + architectures = [var.lambda_arch] + runtime = var.lambda_runtime + publish = true + layers = local.lambda_layer_arns + create_package = false + s3_existing_package = { + bucket = aws_s3_object.lambda_artifact-cpfValidation.bucket + key = aws_s3_object.lambda_artifact-cpfValidation.key + } + + role_name = "lambda-role-cpfValidation" + attach_policy = true + policy = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + # TODO: we need a policy for calling an API endpoint on the application for validation +} From f11d611c4363f6e9ae5a04952cba0152c92ae9a0 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Sun, 7 Jan 2024 20:47:14 -0500 Subject: [PATCH 3/7] include the current environment in the uploads bucket name --- api/src/lib/aws.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/lib/aws.ts b/api/src/lib/aws.ts index 95b12282..39201dd3 100644 --- a/api/src/lib/aws.ts +++ b/api/src/lib/aws.ts @@ -14,7 +14,7 @@ import { import { getSignedUrl as awsGetSignedUrl } from '@aws-sdk/s3-request-presigner' import { QueryResolvers, CreateUploadInput } from 'types/graphql' -const CPF_REPORTER_BUCKET_NAME = 'cpf-reporter' +const CPF_REPORTER_BUCKET_NAME = `cpf-reporter-${process.env.environment}` function getS3Client() { let s3: S3Client From 318d1bf6d14d6f184fa2915f1c1b132890413a30 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Mon, 8 Jan 2024 08:37:11 -0500 Subject: [PATCH 4/7] create a stub implementation of the cpfValidation lambda --- .../cpfValidation/cpfValidation.test.ts | 70 +++++++++++++------ .../functions/cpfValidation/cpfValidation.ts | 48 ++++++------- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/api/src/functions/cpfValidation/cpfValidation.test.ts b/api/src/functions/cpfValidation/cpfValidation.test.ts index 3f71af44..8153683c 100644 --- a/api/src/functions/cpfValidation/cpfValidation.test.ts +++ b/api/src/functions/cpfValidation/cpfValidation.test.ts @@ -1,29 +1,57 @@ -import { mockHttpEvent } from '@redwoodjs/testing/api' +// import { S3EventRecord } from 'aws-lambda' -import { handler } from './cpfValidation' +// import { handler } from './cpfValidation' // Improve this test with help from the Redwood Testing Doc: // https://redwoodjs.com/docs/testing#testing-functions describe('cpfValidation function', () => { - it('Should respond with 200', async () => { - const httpEvent = mockHttpEvent({ - queryStringParameters: { - id: '42', // Add parameters here - }, - }) - - const response = await handler(httpEvent, null) - const { data } = JSON.parse(response.body) - - expect(response.statusCode).toBe(200) - expect(data).toBe('cpfValidation function') + it('Dummy test', () => { + expect(1 + 1).toBe(2) }) - - // You can also use scenarios to test your api functions - // See guide here: https://redwoodjs.com/docs/testing#scenarios - // - // scenario('Scenario test', async () => { - // - // }) + // it('Should respond with 200', async () => { + // const record: S3EventRecord = { + // eventVersion: '2.0', + // eventSource: 'aws:s3', + // eventName: 'ObjectCreated:Put', + // eventTime: '1970-01-01T00:00:00.000Z', + // userIdentity: { principalId: 'test-principalId' }, + // requestParameters: { sourceIPAddress: 'test-sourceIPAddress' }, + // responseElements: { + // 'x-amz-request-id': 'test-x-amz-request-id', + // 'x-amz-id-2': 'test-x-amz-id-2', + // }, + // awsRegion: 'us-east-1', + // s3: { + // s3SchemaVersion: '1.0', + // configurationId: 'test-configurationId', + // bucket: { + // name: 'test-bucket', + // arn: 'test-arn', + // ownerIdentity: { + // principalId: 'test-principalId', + // }, + // }, + // object: { + // key: 'test-key', + // size: 1234, + // eTag: 'test-etag', + // sequencer: 'test-sequencer', + // }, + // }, + // } + // const s3Event = { + // Records: [record], + // } + // const response = await handler(s3Event, null, null) + // const { data } = JSON.parse(response.body) + // expect(response.statusCode).toBe(200) + // expect(data).toBe('excelToJson function') }) + +// You can also use scenarios to test your api functions +// See guide here: https://redwoodjs.com/docs/testing#scenarios +// +// scenario('Scenario test', async () => { +// +// }) diff --git a/api/src/functions/cpfValidation/cpfValidation.ts b/api/src/functions/cpfValidation/cpfValidation.ts index c3edb132..98be06fc 100644 --- a/api/src/functions/cpfValidation/cpfValidation.ts +++ b/api/src/functions/cpfValidation/cpfValidation.ts @@ -1,33 +1,27 @@ -import type { APIGatewayEvent, Context } from 'aws-lambda' +import { https } from 'https' + +import { S3Event, S3Handler } from 'aws-lambda' import { logger } from 'src/lib/logger' -/** - * The handler function is your code that processes http request events. - * You can use return and throw to send a response or error, respectively. - * - * Important: When deployed, a custom serverless function is an open API endpoint and - * is your responsibility to secure appropriately. - * - * @see {@link https://redwoodjs.com/docs/serverless-functions#security-considerations|Serverless Function Considerations} - * in the RedwoodJS documentation for more information. - * - * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent - * @typedef { import('aws-lambda').Context } Context - * @param { APIGatewayEvent } event - an object which contains information from the invoker. - * @param { Context } context - contains information about the invocation, - * function, and execution environment. - */ -export const handler = async (event: APIGatewayEvent, _context: Context) => { - logger.info(`${event.httpMethod} ${event.path}: cpfValidation function`) +const apiEndpoint = 'https://example.com' + +export const handler: S3Handler = async (event: S3Event): Promise => { + try { + const bucket = event.Records[0].s3.bucket.name + const key = event.Records[0].s3.object.key + + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + } - return { - statusCode: 200, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: 'cpfValidation function', - }), + // call API endpoint with S3 key + https.request(apiEndpoint, options, (res) => {}) + } catch (error) { + logger.error('Error processing S3 event:', error) + throw error } } From bbd864dddcdee5633fff135bc7522f04069b4a98 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Mon, 8 Jan 2024 08:59:25 -0500 Subject: [PATCH 5/7] terraform fmt on functions.tf --- terraform/functions.tf | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/terraform/functions.tf b/terraform/functions.tf index 411dca71..a5958b74 100644 --- a/terraform/functions.tf +++ b/terraform/functions.tf @@ -183,8 +183,8 @@ resource "aws_s3_bucket_notification" "json_notification" { lambda_function { lambda_function_arn = module.lambda_function-cpfValidation.lambda_function_arn - events = ["s3:ObjectCreated:*"] - filter_suffix = ".json" + events = ["s3:ObjectCreated:*"] + filter_suffix = ".json" } } @@ -278,25 +278,25 @@ module "lambda_function-excelToJson" { module.lambda_security_group.id, module.postgres.security_group_id, ] - handler = "index.handler" - architectures = [var.lambda_arch] - runtime = var.lambda_runtime - publish = true - layers = local.lambda_layer_arns + handler = "index.handler" + architectures = [var.lambda_arch] + runtime = var.lambda_runtime + publish = true + layers = local.lambda_layer_arns create_package = false s3_existing_package = { bucket = aws_s3_object.lambda_artifact-excelToJson.bucket key = aws_s3_object.lambda_artifact-excelToJson.key } - role_name = "lambda-role-excelToJson" + role_name = "lambda-role-excelToJson" attach_policy = true - policy = "arn:aws:iam::aws:policy/AmazonS3FullAccess" + policy = "arn:aws:iam::aws:policy/AmazonS3FullAccess" } module "lambda_function-cpfValidation" { - source = "terraform-aws-modules/lambda/aws" - version = "6.5.0" + source = "terraform-aws-modules/lambda/aws" + version = "6.5.0" function_name = "cpf-validation" description = "Reacts to S3 events and validates CPF JSON files." @@ -305,19 +305,19 @@ module "lambda_function-cpfValidation" { module.lambda_security_group.id, module.postgres.security_group_id, ] - handler = "index.handler" - architectures = [var.lambda_arch] - runtime = var.lambda_runtime - publish = true - layers = local.lambda_layer_arns + handler = "index.handler" + architectures = [var.lambda_arch] + runtime = var.lambda_runtime + publish = true + layers = local.lambda_layer_arns create_package = false s3_existing_package = { bucket = aws_s3_object.lambda_artifact-cpfValidation.bucket key = aws_s3_object.lambda_artifact-cpfValidation.key } - - role_name = "lambda-role-cpfValidation" + + role_name = "lambda-role-cpfValidation" attach_policy = true - policy = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + policy = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" # TODO: we need a policy for calling an API endpoint on the application for validation } From 53d389a534e82d6501eb76abc5c49451a04fc1e7 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Mon, 8 Jan 2024 09:18:35 -0500 Subject: [PATCH 6/7] add notification for excel files --- terraform/functions.tf | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/terraform/functions.tf b/terraform/functions.tf index a5958b74..74cc8adb 100644 --- a/terraform/functions.tf +++ b/terraform/functions.tf @@ -179,7 +179,7 @@ resource "aws_s3_object" "lambda_artifact-cpfValidation" { } resource "aws_s3_bucket_notification" "json_notification" { - bucket = aws_s3_bucket.cpf_uploads_bucket.bucket + bucket = module.cpf_uploads_bucket.bucket_id lambda_function { lambda_function_arn = module.lambda_function-cpfValidation.lambda_function_arn @@ -188,6 +188,16 @@ resource "aws_s3_bucket_notification" "json_notification" { } } +resource "aws_s3_bucket_notification" "excel_notification" { + bucket = module.cpf_uploads_bucket.bucket_id + + lambda_function { + lambda_function_arn = module.lambda_function-excelToJson.lambda_function_arn + events = ["s3:ObjectCreated:*"] + filter_suffix = ".xlsm" + } +} + module "lambda_function-graphql" { source = "terraform-aws-modules/lambda/aws" version = "6.5.0" From e1fa40d0c73d1cf1e7d5fe99778826722635ae07 Mon Sep 17 00:00:00 2001 From: "Zach A. Thomas" Date: Mon, 8 Jan 2024 09:35:06 -0500 Subject: [PATCH 7/7] temporarily move PrivateSet routes to public --- web/src/Routes.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/web/src/Routes.tsx b/web/src/Routes.tsx index 9a73e69a..3c81d8cf 100644 --- a/web/src/Routes.tsx +++ b/web/src/Routes.tsx @@ -40,6 +40,29 @@ const Routes = () => { + {/* Uploads */} + + + + + + {/* Agencies */} + + + + + {/* Users */} + + + + + {/* Reporting Periods */} + + {/* Organizations */} + + + +