Skip to content

Commit

Permalink
add update practitioner route
Browse files Browse the repository at this point in the history
  • Loading branch information
huang0h committed Aug 2, 2024
1 parent 462bd3e commit 0ca6d1d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 18 deletions.
59 changes: 42 additions & 17 deletions apps/monarch/monarch-backend/src/dynamodb.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { DynamoDBClient, ScanCommand, PutItemCommand, GetItemCommand, DeleteItemCommand } from '@aws-sdk/client-dynamodb';
import { DynamoDBClient, ScanCommand, PutItemCommand, UpdateItemCommand, GetItemCommand, DeleteItemCommand, ReturnValue, AttributeValue, UpdateTimeToLiveCommand } from '@aws-sdk/client-dynamodb';
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
import { Practitioner as practitionerSchema, PractitionerInfo as practitionerInfoSchema } from '@c4c/monarch/common';
import type { Key, Practitioner } from '@c4c/monarch/common';
import { Practitioner as practitionerSchema, PractitionerInfo as practitionerInfoSchema, PractitionerInfo } from '@c4c/monarch/common';
import { Key, Practitioner } from '@c4c/monarch/common';
import { Request } from 'express';
import { ZodObject, ZodRawShape } from 'zod';

if (process.env.AWS_ACCESS_KEY_ID == null) {
throw new Error('AWS Access Key not configured');
Expand All @@ -14,7 +15,17 @@ if (process.env.AWS_SECRET_ACCESS_KEY == null) {
const client = new DynamoDBClient({ region: 'us-east-2' });

// TODO: Write persitence level test using local DB
// This is kind of a pain, and a "slow" test so we elide it
// This is kind of a pain, and a "slow" test so we elide it`

// Validates and extracts practitioner data from a request bdoy
function practitionerDataFromBody(request: Request, schema, keyPrefix: string = '') {
const parsedBody = schema.parse(request.body)
// List of fields on Practitioner model
const practitionerProperties = schema.keyof().options;
const pairs = practitionerProperties.map(prop => [keyPrefix + prop, parsedBody[prop]])

return Object.fromEntries(pairs);
}

export async function scanAllPractitioners(): Promise<Practitioner[]> {
const command = new ScanCommand({
Expand Down Expand Up @@ -57,20 +68,10 @@ export async function postPractitioner(req: Request): Promise<Practitioner> {

const parameters = {
TableName: 'PractitionersV2',
Item: marshall({
uuid: req.body.uuid,
phoneNumber: req.body.phoneNumber,
fullName: req.body.fullName,
businessLocation: req.body.businessLocation,
businessName: req.body.businessName,
email: req.body.email,
geocode: req.body.geocode,
languagesList: req.body.languagesList,
minAgeServed: req.body.minAgeServed,
modality: req.body.modality,
website: req.body.website,
Item: marshall({
...practitionerDataFromBody(req, practitionerInfoSchema),
dateJoined: nowString,
familiesHelped: 0,
familiesHelped: 0
}),
};

Expand All @@ -90,6 +91,30 @@ export async function postPractitioner(req: Request): Promise<Practitioner> {
return practitionerSchema.parse(unmarshall(practitioner.Item));
}

export async function updatePractitioner(req: Request): Promise<Practitioner> {
// Remove UUID from list of fields since we cannot update that it (and shouldn't, anyways)
const practitionerKeys = Practitioner.omit({ uuid: true }).keyof().options;
// Prefix with ':' to use as expression attribute values
const practitionerData = practitionerDataFromBody(req, practitionerSchema.omit({ uuid: true }), ':');

// Map keys to expression e.g. "phoneNumber" => "phoneNumber=:phoneNumber"
const updateExpression = `SET ${practitionerKeys.map(k => `${k} = :${k}`).join(', ')}`
const updateItemParameters = {
TableName: 'PractitionersV2',
Key: {
uuid: { "S": req.body.uuid }
},
ExpressionAttributeValues: marshall(practitionerData),
UpdateExpression: updateExpression,
ReturnValues: "ALL_NEW"
}

const updateCommand = new UpdateItemCommand(updateItemParameters)
const { Attributes: updatedAttributes } = await client.send(updateCommand);

return practitionerSchema.parse(unmarshall(updatedAttributes));
}

export async function deletePractitioner(req: Request): Promise<Key> {
const parameters = {
TableName: 'PractitionersV2',
Expand Down
12 changes: 11 additions & 1 deletion apps/monarch/monarch-backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import cors from 'cors';
import getAllPractitioners from './workflows/getAllPractitioners';
import getPendingPractitioners from './workflows/getPendingPractitioners';
import postNewPractitioner from './workflows/postNewPractitioner';
import updatePractitionerWF from './workflows/updatePractitioner';
import deletePractitionerWF from './workflows/deletePractitioner';
import deletePendingPractitionerWF from './workflows/deletePendingPractitioner';
import getGeocode from './workflows/getGeocode';
// Import effectful dependencies (database connections, email clients, etc.)
import { scanAllPractitioners, scanPendingPractitioners, postPractitioner, deletePractitioner, deletePendingPractitioner } from './dynamodb';
import { scanAllPractitioners, scanPendingPractitioners, postPractitioner, updatePractitioner, deletePractitioner, deletePendingPractitioner } from './dynamodb';
import { extractGeocode } from './location';
import { zodiosApp } from '@zodios/express';
import { userApi, isValidZipcode } from '@c4c/monarch/common';
Expand Down Expand Up @@ -40,6 +41,10 @@ const postPractitionerHandler = async (req: Request) => {
return postNewPractitioner(req, postPractitioner);
}

const updatePractitionerHandler = async(req: Request) => {
return updatePractitionerWF(req, updatePractitioner);
}

const deletePractitionerHandler = async (req: Request) => {
return deletePractitionerWF(req, deletePractitioner);
}
Expand Down Expand Up @@ -114,6 +119,11 @@ authenticatedRoute.post('/practitioners', async (req: Request, res: Response) =>
res.status(201).json(practitioner).end();
});

authenticatedRoute.put('/practitioners', async (req: Request, res: Response) => {
const practitioner = await updatePractitionerHandler(req);
res.status(201).json(practitioner).end();
});

authenticatedRoute.get('/pendingPractitioners', async (req: Request, res: Response) => {
const practitioners = await getPendingPractitionersHandler(req);
res.status(200).json(practitioners).end();
Expand Down
13 changes: 13 additions & 0 deletions apps/monarch/monarch-backend/src/workflows/updatePractitioner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Practitioner } from "@c4c/monarch/common";
import { Request } from "express";

async function updatePractitionerWF(req: Request, updatePractitioner: (req: Request) => Promise<Practitioner>) {
try {
return updatePractitioner(req);
} catch (e) {
console.log(e);
throw new Error("Unable to update practitioner");
}
}

export default updatePractitionerWF;
21 changes: 21 additions & 0 deletions libs/monarch/common/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ export const userApi = makeApi([
}
]
},
{
method: "put",
path: "/practitioners",
alias: "updatePractitioner",
description: "Update Practitioner",
response: Practitioner,
parameters: [
{
name: 'practitioner',
description: 'Updated practitioner data',
schema: Practitioner,
type: 'Body',
},
{
name: 'accessToken',
description: 'Admin Access Token',
schema: z.string(),
type: 'Header',
}
]
},
{
method: "delete",
path: "/practitioners",
Expand Down

0 comments on commit 0ca6d1d

Please sign in to comment.