From 31754f50be6722b98f739041c4775e55c9e85a9f Mon Sep 17 00:00:00 2001 From: Megan Li Date: Sat, 15 Apr 2023 01:17:50 -0400 Subject: [PATCH] allow admins to view and create notes --- apps/backend/src/db/qldb.ts | 6 +- apps/backend/src/db/utils.ts | 11 +++ apps/backend/src/handlers/patchForm.ts | 55 +++++++++++++ apps/backend/src/schema/schema.ts | 5 +- apps/backend/template.yaml | 20 ++++- .../src/components/adminNotes/AdminNotes.tsx | 78 +++++++++++++++++++ apps/frontend/src/constants/endpoints.ts | 5 +- apps/frontend/src/pages/OneFormPage.tsx | 4 +- apps/frontend/src/types/formSchema.ts | 13 +++- apps/frontend/src/utils/sendRequest.ts | 10 ++- 10 files changed, 193 insertions(+), 14 deletions(-) create mode 100644 apps/backend/src/handlers/patchForm.ts create mode 100644 apps/frontend/src/components/adminNotes/AdminNotes.tsx diff --git a/apps/backend/src/db/qldb.ts b/apps/backend/src/db/qldb.ts index 66013b4..b391127 100644 --- a/apps/backend/src/db/qldb.ts +++ b/apps/backend/src/db/qldb.ts @@ -1,10 +1,6 @@ import { QLDBSessionClientConfig } from '@aws-sdk/client-qldb-session'; import { NodeHttpHandlerOptions } from '@aws-sdk/node-http-handler'; -import { - QldbDriver, - RetryConfig, - TransactionExecutor, -} from 'amazon-qldb-driver-nodejs'; +import { QldbDriver, RetryConfig } from 'amazon-qldb-driver-nodejs'; import { Agent } from 'https'; // IMPORTANT: this must match the ledger name defined in template.yaml diff --git a/apps/backend/src/db/utils.ts b/apps/backend/src/db/utils.ts index 818a4f0..591c947 100644 --- a/apps/backend/src/db/utils.ts +++ b/apps/backend/src/db/utils.ts @@ -1,3 +1,4 @@ +import { AdminNotes } from '../schema/schema.js'; import { qldbDriver, tableName } from './qldb.js'; export async function insertDocument( @@ -24,3 +25,13 @@ export async function fetchDocumentById(id: string) { return result.getResultList(); }); } + +export async function updateDocumentAdminNotes(id: string, notes: AdminNotes) { + return await qldbDriver.executeLambda(async (txn) => { + return await txn.execute( + `UPDATE ${tableName} as f SET f.adminNotes = ? where f.id = ?`, + notes, + id + ); + }); +} diff --git a/apps/backend/src/handlers/patchForm.ts b/apps/backend/src/handlers/patchForm.ts new file mode 100644 index 0000000..3f6210c --- /dev/null +++ b/apps/backend/src/handlers/patchForm.ts @@ -0,0 +1,55 @@ +import { APIGatewayEvent } from 'aws-lambda'; +import { createTableIfNotExists } from '../db/createTable.js'; +import { updateDocumentAdminNotes } from '../db/utils.js'; +import { AdminNotes, adminNotesSchema } from '../schema/schema.js'; +/** + * An HTTP post method to add one form to the QLDB table. + */ +export const patchFormHandler = async (event: APIGatewayEvent) => { + const headers = { + 'Access-Control-Allow-Headers': 'Content-Type, Access-Control-Allow-Origin', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'PATCH', + }; + + if (event.httpMethod !== 'PATCH') { + return { + statusCode: 400, + headers, + body: `patchMethod only accepts PATCH method, you tried: ${event.httpMethod} method.`, + }; + } + // All log statements are written to CloudWatch + console.info('received:', event); + + const id = event.pathParameters!.id!; + const JSONbody = JSON.parse(event.body!); + let notes: AdminNotes; + try { + notes = adminNotesSchema.parse(JSONbody); + } catch (error) { + const errorMessage = 'Admin notes does not match schema. Error: ' + error; + console.error(errorMessage); + return { + statusCode: 400, + headers, + body: errorMessage, + }; + } + + try { + await createTableIfNotExists(); + await updateDocumentAdminNotes(id, notes); + return { + statusCode: 201, + headers, + }; + } catch (error) { + console.error(error); + return { + statusCode: 500, + headers, + body: 'Error updating database.', + }; + } +}; diff --git a/apps/backend/src/schema/schema.ts b/apps/backend/src/schema/schema.ts index b712adc..73b57a5 100644 --- a/apps/backend/src/schema/schema.ts +++ b/apps/backend/src/schema/schema.ts @@ -20,6 +20,9 @@ const adminNoteSchema = z.object({ updatedAt: dateSchema, }); +export const adminNotesSchema = adminNoteSchema.array(); +export type AdminNotes = z.infer; + // Part of form to be filled out by the child's parent/legal guardian const guardianFormSchema = z.object({ childsName: z.string().min(1), @@ -60,5 +63,5 @@ export const formSchema = z.object({ id: z.string(), guardianForm: guardianFormSchema, medicalForm: medicalFormSchema, - adminNotes: adminNoteSchema.array(), + adminNotes: adminNotesSchema, }); diff --git a/apps/backend/template.yaml b/apps/backend/template.yaml index addbfe3..30fd56f 100644 --- a/apps/backend/template.yaml +++ b/apps/backend/template.yaml @@ -21,7 +21,7 @@ Globals: Api: TracingEnabled: true Cors: - AllowMethods: "'GET,POST,OPTIONS'" + AllowMethods: "'GET,POST,PATCH,OPTIONS'" AllowHeaders: "'content-type, Access-Control-Allow-Origin'" AllowOrigin: "'*'" @@ -142,6 +142,24 @@ Resources: Method: POST RestApiId: !Ref ApiGateway + patchFormFunction: + Type: AWS::Serverless::Function + Properties: + Handler: dist/handlers/patchForm.patchFormHandler + Runtime: nodejs18.x + Architectures: + - x86_64 + MemorySize: 128 + Timeout: 100 + Description: An HTTP patch method to update a document in the QLDB table. + Role: !GetAtt QLDBSendCommandRole.Arn + Events: + Api: + Type: Api + Properties: + Path: /form/{id}/notes + Method: PATCH + RestApiId: !Ref ApiGateway # Creates an AWS KMS key to encrypt/decrypt data in QLDB # the key policy gives root account users key management permissions # and gives the IAM role given to the Lambdas permission to use the key (necessary to interact with QLDB) diff --git a/apps/frontend/src/components/adminNotes/AdminNotes.tsx b/apps/frontend/src/components/adminNotes/AdminNotes.tsx new file mode 100644 index 0000000..bc1c593 --- /dev/null +++ b/apps/frontend/src/components/adminNotes/AdminNotes.tsx @@ -0,0 +1,78 @@ +import { Button, Flex, Heading, Text, Textarea } from '@chakra-ui/react'; +import { useState } from 'react'; +import { AdminNotes } from '../../types/formSchema'; +import { patchAdminNotes } from '../../utils/sendRequest'; +import { useParams } from 'react-router-dom'; + +interface AdminNotesProps { + notes: AdminNotes; +} + +export const ViewAdminNotes: React.FC = ({ notes }) => { + const [savedNotes, setSavedNotes] = useState(notes); + const [newNote, setNewNote] = useState(''); + const [error, setError] = useState(null); + + const { id } = useParams(); + const onSave = () => { + if (id) { + const newSavedNotes = [ + { note: newNote, updatedAt: new Date() }, + ...savedNotes, + ]; + patchAdminNotes(id, newSavedNotes) + .then(() => { + setSavedNotes(newSavedNotes); + setNewNote(''); + }) + .catch((e) => { + setError('Error encountered while saving note.'); + console.error(e); + }); + } + }; + return ( + + + Admin Notes + + + {error && ( + + {error} + + )} + +