From 4668df8dcf1e044829f121406e97364dc114d73c Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 27 Sep 2024 10:59:11 -0600 Subject: [PATCH 01/14] 10492: Move User Case Notes to Postgres --- .../caseNote/deleteUserCaseNoteInteractor.ts | 4 +- .../getUserCaseNoteForCasesInteractor.ts | 15 +++---- .../caseNote/getUserCaseNoteInteractor.ts | 12 +++--- .../caseNote/updateUserCaseNoteInteractor.ts | 4 +- web-api/src/database-types.ts | 11 ++++++ web-api/src/getPersistenceGateway.ts | 8 ---- .../userCaseNotes/deleteUserCaseNote.test.ts | 27 ------------- .../userCaseNotes/deleteUserCaseNote.ts | 27 ------------- .../userCaseNotes/getUserCaseNote.test.ts | 39 ------------------- .../dynamo/userCaseNotes/getUserCaseNote.ts | 27 ------------- .../getUserCaseNoteForCases.test.ts | 35 ----------------- .../userCaseNotes/getUserCaseNoteForCases.ts | 27 ------------- .../userCaseNotes/updateUserCaseNote.test.ts | 31 --------------- .../userCaseNotes/updateUserCaseNote.ts | 25 ------------ .../userCaseNotes/deleteUserCaseNote.ts | 17 ++++++++ .../postgres/userCaseNotes/getUserCaseNote.ts | 22 +++++++++++ .../userCaseNotes/getUserCaseNoteForCases.ts | 24 ++++++++++++ .../postgres/userCaseNotes/mocks.jest.ts | 21 ++++++++++ .../userCaseNotes/updateUserCaseNote.ts | 23 +++++++++++ .../migrations/0004-add-user-case-notes.ts | 15 +++++++ 20 files changed, 148 insertions(+), 266 deletions(-) delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.test.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.test.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.test.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.test.ts delete mode 100644 web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/deleteUserCaseNote.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/getUserCaseNote.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/getUserCaseNoteForCases.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts create mode 100644 web-api/src/persistence/postgres/utils/migrate/migrations/0004-add-user-case-notes.ts diff --git a/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.ts b/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.ts index 65809d66eaf..4445f8c31a8 100644 --- a/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.ts +++ b/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.ts @@ -5,6 +5,7 @@ import { import { ServerApplicationContext } from '@web-api/applicationContext'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; +import { deleteUserCaseNote } from '@web-api/persistence/postgres/userCaseNotes/deleteUserCaseNote'; /** * deleteUserCaseNoteInteractor @@ -31,8 +32,7 @@ export const deleteUserCaseNoteInteractor = async ( userIdMakingRequest: authorizedUser.userId, }); - return await applicationContext.getPersistenceGateway().deleteUserCaseNote({ - applicationContext, + return await deleteUserCaseNote({ docketNumber, userId, }); diff --git a/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.ts b/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.ts index 91257ab2877..613b2779468 100644 --- a/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.ts +++ b/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.ts @@ -4,7 +4,7 @@ import { } from '../../../../../shared/src/authorization/authorizationClientService'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; -import { UserCaseNote } from '../../../../../shared/src/business/entities/notes/UserCaseNote'; +import { getUserCaseNoteForCases } from '@web-api/persistence/postgres/userCaseNotes/getUserCaseNoteForCases'; export const getUserCaseNoteForCasesInteractor = async ( applicationContext, @@ -21,13 +21,10 @@ export const getUserCaseNoteForCasesInteractor = async ( userIdMakingRequest: authorizedUser.userId, }); - const caseNotes = await applicationContext - .getPersistenceGateway() - .getUserCaseNoteForCases({ - applicationContext, - docketNumbers, - userId, - }); + const caseNotes = await getUserCaseNoteForCases({ + docketNumbers, + userId, + }); - return caseNotes.map(note => new UserCaseNote(note).validate().toRawObject()); + return caseNotes.map(note => note.validate().toRawObject()); }; diff --git a/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.ts b/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.ts index d7c1c37af7d..17bd2fb6e29 100644 --- a/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.ts +++ b/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.ts @@ -6,6 +6,7 @@ import { ServerApplicationContext } from '@web-api/applicationContext'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { UserCaseNote } from '../../../../../shared/src/business/entities/notes/UserCaseNote'; +import { getUserCaseNote } from '@web-api/persistence/postgres/userCaseNotes/getUserCaseNote'; /** * getUserCaseNoteInteractor @@ -32,13 +33,10 @@ export const getUserCaseNoteInteractor = async ( userIdMakingRequest: authorizedUser.userId, }); - const caseNote = await applicationContext - .getPersistenceGateway() - .getUserCaseNote({ - applicationContext, - docketNumber, - userId, - }); + const caseNote = await getUserCaseNote({ + docketNumber, + userId, + }); if (caseNote) { return new UserCaseNote(caseNote).validate().toRawObject(); diff --git a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts index b4894a0a629..da81f2f1b5e 100644 --- a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts +++ b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts @@ -5,6 +5,7 @@ import { import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { UserCaseNote } from '../../../../../shared/src/business/entities/notes/UserCaseNote'; +import { updateUserCaseNote } from '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote'; export const updateUserCaseNoteInteractor = async ( applicationContext, @@ -31,8 +32,7 @@ export const updateUserCaseNoteInteractor = async ( const caseNoteToUpdate = caseNoteEntity.validate().toRawObject(); - await applicationContext.getPersistenceGateway().updateUserCaseNote({ - applicationContext, + await updateUserCaseNote({ caseNoteToUpdate, }); diff --git a/web-api/src/database-types.ts b/web-api/src/database-types.ts index dd32ee374bf..089a8041873 100644 --- a/web-api/src/database-types.ts +++ b/web-api/src/database-types.ts @@ -3,6 +3,7 @@ import { ColumnType, Insertable, Selectable, Updateable } from 'kysely'; export interface Database { message: MessageTable; case: CaseTable; + dwUserCaseNote: UserCaseNoteTable; } export interface MessageTable { @@ -47,3 +48,13 @@ export interface CaseTable { export type CaseKysely = Selectable; export type NewCaseKysely = Insertable; export type UpdateCaseKysely = Updateable; + +export interface UserCaseNoteTable { + docketNumber: string; + userId: string; + notes?: string; +} + +export type UserCaseNoteKysely = Selectable; +export type NewUserCaseNoteKysely = Insertable; +export type UpdateUserCaseNoteKysely = Updateable; diff --git a/web-api/src/getPersistenceGateway.ts b/web-api/src/getPersistenceGateway.ts index fb449d28cfb..1c3cecf0f19 100644 --- a/web-api/src/getPersistenceGateway.ts +++ b/web-api/src/getPersistenceGateway.ts @@ -42,7 +42,6 @@ import { deletePractitionerDocument } from './persistence/dynamo/practitioners/d import { deleteRecord } from './persistence/elasticsearch/deleteRecord'; import { deleteTrialSession } from './persistence/dynamo/trialSessions/deleteTrialSession'; import { deleteTrialSessionWorkingCopy } from './persistence/dynamo/trialSessions/deleteTrialSessionWorkingCopy'; -import { deleteUserCaseNote } from './persistence/dynamo/userCaseNotes/deleteUserCaseNote'; import { deleteUserConnection } from './persistence/dynamo/notifications/deleteUserConnection'; import { deleteUserFromCase } from './persistence/dynamo/cases/deleteUserFromCase'; import { deleteWorkItem } from './persistence/dynamo/workitems/deleteWorkItem'; @@ -115,8 +114,6 @@ import { getTrialSessions } from './persistence/dynamo/trialSessions/getTrialSes import { getUploadPolicy } from './persistence/s3/getUploadPolicy'; import { getUserByEmail } from './persistence/dynamo/users/getUserByEmail'; import { getUserById } from './persistence/dynamo/users/getUserById'; -import { getUserCaseNote } from './persistence/dynamo/userCaseNotes/getUserCaseNote'; -import { getUserCaseNoteForCases } from './persistence/dynamo/userCaseNotes/getUserCaseNoteForCases'; import { getUsersById } from './persistence/dynamo/users/getUsersById'; import { getUsersBySearchKey } from './persistence/dynamo/users/getUsersBySearchKey'; import { getUsersInSection } from './persistence/dynamo/users/getUsersInSection'; @@ -163,7 +160,6 @@ import { updatePractitionerUser } from './persistence/dynamo/users/updatePractit import { updateTrialSession } from './persistence/dynamo/trialSessions/updateTrialSession'; import { updateTrialSessionWorkingCopy } from './persistence/dynamo/trialSessions/updateTrialSessionWorkingCopy'; import { updateUser } from './persistence/dynamo/users/updateUser'; -import { updateUserCaseNote } from './persistence/dynamo/userCaseNotes/updateUserCaseNote'; import { updateUserRecords } from './persistence/dynamo/users/updateUserRecords'; import { uploadDocument } from '@web-api/persistence/s3/uploadDocument'; import { verifyCaseForUser } from './persistence/dynamo/cases/verifyCaseForUser'; @@ -256,7 +252,6 @@ const gatewayMethods = { updateTrialSession, updateTrialSessionWorkingCopy, updateUser, - updateUserCaseNote, updateUserRecords, }), // methods below are not known to create or update "entity" records @@ -276,7 +271,6 @@ const gatewayMethods = { deleteRecord, deleteTrialSession, deleteTrialSessionWorkingCopy, - deleteUserCaseNote, deleteUserConnection, deleteUserFromCase, deleteWorkItem, @@ -347,8 +341,6 @@ const gatewayMethods = { getUploadPolicy, getUserByEmail, getUserById, - getUserCaseNote, - getUserCaseNoteForCases, getUsersById, getUsersBySearchKey, getUsersInSection, diff --git a/web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.test.ts b/web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.test.ts deleted file mode 100644 index 3a285c0efdf..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; -import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; -import { deleteUserCaseNote } from './deleteUserCaseNote'; -import { remove } from '../../dynamodbClientService'; - -jest.mock('../../dynamodbClientService', () => ({ - remove: jest.fn(), -})); - -describe('deleteUserCaseNote', () => { - const USER_ID = '10ecc428-ca35-4e36-aef2-e844660ce22d'; - - it('attempts to delete the case note', async () => { - await deleteUserCaseNote({ - applicationContext, - docketNumber: MOCK_CASE.docketNumber, - userId: USER_ID, - }); - - expect((remove as jest.Mock).mock.calls[0][0]).toMatchObject({ - key: { - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - }, - }); - }); -}); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.ts b/web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.ts deleted file mode 100644 index 82c8d0397c4..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/deleteUserCaseNote.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { remove } from '../../dynamodbClientService'; - -/** - * deleteUserCaseNote - * - * @param {object} providers the providers object - * @param {object} providers.applicationContext the application context - * @param {string} providers.docketNumber the docket number of the case the notes are associated with - * @param {string} providers.userId the id of the user who owns the case notes - * @returns {Array} the promises for the persistence delete calls - */ -export const deleteUserCaseNote = ({ - applicationContext, - docketNumber, - userId, -}: { - applicationContext: IApplicationContext; - docketNumber: string; - userId: string; -}) => - remove({ - applicationContext, - key: { - pk: `user-case-note|${docketNumber}`, - sk: `user|${userId}`, - }, - }); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.test.ts b/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.test.ts deleted file mode 100644 index 566e7849351..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; -import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; -import { get } from '../../dynamodbClientService'; -import { getUserCaseNote } from './getUserCaseNote'; - -const USER_ID = '220b5dc9-9d0c-4662-97ad-2cb9729c611a'; - -jest.mock('../../dynamodbClientService', () => ({ - get: jest.fn().mockReturnValue({ - notes: 'something', - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - userId: USER_ID, - }), -})); - -describe('getUserCaseNote', () => { - it('should get the case notes using docket number and user id', async () => { - const result = await getUserCaseNote({ - applicationContext, - docketNumber: MOCK_CASE.docketNumber, - userId: USER_ID, - }); - - expect((get as jest.Mock).mock.calls[0][0]).toMatchObject({ - Key: { - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - }, - }); - - expect(result).toEqual({ - notes: 'something', - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - userId: USER_ID, - }); - }); -}); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.ts b/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.ts deleted file mode 100644 index b7b9f4d0566..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNote.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { get } from '../../dynamodbClientService'; - -/** - * getUserCaseNote - * - * @param {object} providers the providers object - * @param {object} providers.applicationContext the application context - * @param {string} providers.docketNumber the docket number of the case to get the case notes for - * @param {string} providers.userId the id of the user to get the case notes for - * @returns {Promise} the promise of the persistence call to get the record - */ -export const getUserCaseNote = ({ - applicationContext, - docketNumber, - userId, -}: { - applicationContext: IApplicationContext; - docketNumber: string; - userId: string; -}) => - get({ - Key: { - pk: `user-case-note|${docketNumber}`, - sk: `user|${userId}`, - }, - applicationContext, - }); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.test.ts b/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.test.ts deleted file mode 100644 index 7d2cdd56c07..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; -import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; -import { getUserCaseNoteForCases } from './getUserCaseNoteForCases'; - -const USER_ID = 'b1edae5a-23e4-4dc8-9d6c-43060ab3d8c7'; - -jest.mock('../../dynamodbClientService', () => ({ - batchGet: jest.fn().mockReturnValue([ - { - notes: 'something', - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - userId: USER_ID, - }, - ]), -})); - -describe('getUserCaseNoteForCases', () => { - it('should get the case notes by case id and user id', async () => { - const result = await getUserCaseNoteForCases({ - applicationContext, - docketNumbers: [MOCK_CASE.docketNumber], - userId: USER_ID, - }); - - expect(result).toEqual([ - { - notes: 'something', - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - userId: USER_ID, - }, - ]); - }); -}); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.ts b/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.ts deleted file mode 100644 index aea2d8f5f9c..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/getUserCaseNoteForCases.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { batchGet } from '../../dynamodbClientService'; - -/** - * getUserCaseNoteForCases - * - * @param {object} providers the providers object - * @param {object} providers.applicationContext the application context - * @param {Array} providers.docketNumbers the docket numbers of the cases to get the case notes for - * @param {string} providers.userId the id of the user to get the case notes for - * @returns {Promise} the promise of the persistence call to get the record - */ -export const getUserCaseNoteForCases = ({ - applicationContext, - docketNumbers, - userId, -}: { - applicationContext: IApplicationContext; - docketNumbers: string[]; - userId: string; -}) => - batchGet({ - applicationContext, - keys: docketNumbers.map(docketNumber => ({ - pk: `user-case-note|${docketNumber}`, - sk: `user|${userId}`, - })), - }); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.test.ts b/web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.test.ts deleted file mode 100644 index 376b3ab973f..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; -import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; -import { put } from '../../dynamodbClientService'; -import { updateUserCaseNote } from './updateUserCaseNote'; - -jest.mock('../../dynamodbClientService', () => ({ - put: jest.fn(), -})); - -describe('updateUserCaseNote', () => { - const USER_ID = '42f68c70-b803-4883-985d-ea1903e31ae2'; - - it('invokes the persistence layer with pk of user-case-note|{docketNumber}, sk of {userId} and other expected params', async () => { - await updateUserCaseNote({ - applicationContext, - caseNoteToUpdate: { - docketNumber: MOCK_CASE.docketNumber, - notes: 'something!!!', - userId: USER_ID, - }, - }); - - expect((put as jest.Mock).mock.calls[0][0]).toMatchObject({ - Item: { - notes: 'something!!!', - pk: `user-case-note|${MOCK_CASE.docketNumber}`, - sk: `user|${USER_ID}`, - }, - }); - }); -}); diff --git a/web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.ts b/web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.ts deleted file mode 100644 index ba08d161fbf..00000000000 --- a/web-api/src/persistence/dynamo/userCaseNotes/updateUserCaseNote.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { put } from '../../dynamodbClientService'; - -/** - * updateUserCaseNote - * - * @param {object} providers the providers object - * @param {object} providers.applicationContext the application context - * @param {object} providers.caseNoteToUpdate the case note data to update - * @returns {Promise} the promise of the call to persistence - */ -export const updateUserCaseNote = ({ - applicationContext, - caseNoteToUpdate, -}: { - applicationContext: IApplicationContext; - caseNoteToUpdate: TCaseNote; -}) => - put({ - Item: { - ...caseNoteToUpdate, - pk: `user-case-note|${caseNoteToUpdate.docketNumber}`, - sk: `user|${caseNoteToUpdate.userId}`, - }, - applicationContext, - }); diff --git a/web-api/src/persistence/postgres/userCaseNotes/deleteUserCaseNote.ts b/web-api/src/persistence/postgres/userCaseNotes/deleteUserCaseNote.ts new file mode 100644 index 00000000000..56af9163386 --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/deleteUserCaseNote.ts @@ -0,0 +1,17 @@ +import { getDbWriter } from '@web-api/database'; + +export const deleteUserCaseNote = async ({ + docketNumber, + userId, +}: { + docketNumber: string; + userId: string; +}) => { + await getDbWriter(writer => + writer + .deleteFrom('dwUserCaseNote') + .where('docketNumber', '=', docketNumber) + .where('userId', '=', userId) + .execute(), + ); +}; diff --git a/web-api/src/persistence/postgres/userCaseNotes/getUserCaseNote.ts b/web-api/src/persistence/postgres/userCaseNotes/getUserCaseNote.ts new file mode 100644 index 00000000000..c6db8c43076 --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/getUserCaseNote.ts @@ -0,0 +1,22 @@ +import { UserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; +import { getDbReader } from '@web-api/database'; +import { transformNullToUndefined } from '@web-api/persistence/postgres/utils/transformNullToUndefined'; + +export const getUserCaseNote = async ({ + docketNumber, + userId, +}: { + docketNumber: string; + userId: string; +}) => { + const userCaseNote = await getDbReader(writer => + writer + .selectFrom('dwUserCaseNote') + .selectAll() + .where('docketNumber', '=', docketNumber) + .where('userId', '=', userId) + .executeTakeFirst(), + ); + + return new UserCaseNote(transformNullToUndefined(userCaseNote)); +}; diff --git a/web-api/src/persistence/postgres/userCaseNotes/getUserCaseNoteForCases.ts b/web-api/src/persistence/postgres/userCaseNotes/getUserCaseNoteForCases.ts new file mode 100644 index 00000000000..6e560147180 --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/getUserCaseNoteForCases.ts @@ -0,0 +1,24 @@ +import { UserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; +import { getDbReader } from '@web-api/database'; +import { transformNullToUndefined } from '@web-api/persistence/postgres/utils/transformNullToUndefined'; + +export const getUserCaseNoteForCases = async ({ + docketNumbers, + userId, +}: { + docketNumbers: string[]; + userId: string; +}) => { + const userCaseNotes = await getDbReader(reader => + reader + .selectFrom('dwUserCaseNote') + .selectAll() + .where('userId', '=', userId) + .where('docketNumber', 'in', docketNumbers) + .execute(), + ); + + return userCaseNotes.map( + userCaseNote => new UserCaseNote(transformNullToUndefined(userCaseNote)), + ); +}; diff --git a/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts b/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts new file mode 100644 index 00000000000..17a4a7e4d4c --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts @@ -0,0 +1,21 @@ +import { mockFactory } from '@shared/test/mockFactory'; + +jest.mock( + '@web-api/persistence/postgres/userCaseNotes/deleteUserCaseNote.ts', + () => mockFactory('deleteUserCaseNote'), +); + +jest.mock( + '@web-api/persistence/postgres/userCaseNotes/getUserCaseNote.ts', + () => mockFactory('getUserCaseNote'), +); + +jest.mock( + '@web-api/persistence/postgres/userCaseNotes/getUserCaseNoteForCases.ts', + () => mockFactory('getUserCaseNoteForCases'), +); + +jest.mock( + '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote.ts', + () => mockFactory('updateUserCaseNote'), +); diff --git a/web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts b/web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts new file mode 100644 index 00000000000..4919e05f31e --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts @@ -0,0 +1,23 @@ +import { getDbWriter } from '@web-api/database'; + +export const updateUserCaseNote = async ({ + caseNoteToUpdate, +}: { + caseNoteToUpdate: TCaseNote; +}) => { + await getDbWriter(writer => + writer + .insertInto('dwUserCaseNote') + .values({ + docketNumber: caseNoteToUpdate.docketNumber, + notes: caseNoteToUpdate.notes, + userId: caseNoteToUpdate.userId, + }) + .onConflict(oc => + oc.columns(['docketNumber', 'userId']).doUpdateSet({ + notes: caseNoteToUpdate.notes, + }), + ) + .execute(), + ); +}; diff --git a/web-api/src/persistence/postgres/utils/migrate/migrations/0004-add-user-case-notes.ts b/web-api/src/persistence/postgres/utils/migrate/migrations/0004-add-user-case-notes.ts new file mode 100644 index 00000000000..6d21457ecbf --- /dev/null +++ b/web-api/src/persistence/postgres/utils/migrate/migrations/0004-add-user-case-notes.ts @@ -0,0 +1,15 @@ +import { Kysely } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('dwUserCaseNote') + .addColumn('docketNumber', 'varchar', col => col.notNull()) + .addColumn('userId', 'varchar', col => col.notNull()) + .addColumn('notes', 'text') + .addPrimaryKeyConstraint('pk_user_case_note', ['docketNumber', 'userId']) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('dwUserCaseNote').execute(); +} From 5e494047a8a84ee20eca600912a3bd5c3af76b25 Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 4 Oct 2024 10:49:28 -0600 Subject: [PATCH 02/14] 10492: update api tests with mocks + add stream for user case notes --- .../deleteUserCaseNoteInteractor.test.ts | 1 + .../getUserCaseNoteForCasesInteractor.test.ts | 1 + .../getUserCaseNoteInteractor.test.ts | 1 + .../updateUserCaseNoteInteractor.test.ts | 1 + .../processStreamRecordsInteractor.test.ts | 5 ++ .../processStreamRecordsInteractor.ts | 15 ++++++ .../processStreamUtilities.ts | 10 +++- .../processUserCaseNoteEntries.test.ts | 46 +++++++++++++++++++ .../processUserCaseNoteEntries.ts | 26 +++++++++++ .../postgres/userCaseNotes/mocks.jest.ts | 5 ++ .../userCaseNotes/upsertUserCaseNotes.ts | 26 +++++++++++ 11 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts create mode 100644 web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNotes.ts diff --git a/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts index d47eb43b837..a54b34e0d96 100644 --- a/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts @@ -1,3 +1,4 @@ +import '@web-api/persistence/postgres/userCaseNotes/mocks.jest'; import { ROLES } from '../../../../../shared/src/business/entities/EntityConstants'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; diff --git a/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts b/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts index 31f054dfec4..322b49c3597 100644 --- a/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts @@ -1,3 +1,4 @@ +import '@web-api/persistence/postgres/userCaseNotes/mocks.jest'; import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; diff --git a/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts index c1e25eacca5..a5845bcb286 100644 --- a/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts @@ -1,3 +1,4 @@ +import '@web-api/persistence/postgres/userCaseNotes/mocks.jest'; import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; diff --git a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts index d907227899e..47d86525197 100644 --- a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts @@ -1,3 +1,4 @@ +import '@web-api/persistence/postgres/userCaseNotes/mocks.jest'; import { ROLES } from '../../../../../shared/src/business/entities/EntityConstants'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; diff --git a/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.test.ts b/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.test.ts index 2ee5a87fc45..62a30a45b8e 100644 --- a/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.test.ts +++ b/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.test.ts @@ -5,6 +5,7 @@ jest.mock('./processPractitionerMappingEntries'); jest.mock('./processRemoveEntries'); jest.mock('./processWorkItemEntries'); jest.mock('./processCaseEntries'); +jest.mock('./processUserCaseNoteEntries'); jest.mock('./processOtherEntries'); import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; import { partitionRecords } from './processStreamUtilities'; @@ -15,6 +16,7 @@ import { processOtherEntries } from './processOtherEntries'; import { processPractitionerMappingEntries } from './processPractitionerMappingEntries'; import { processRemoveEntries } from './processRemoveEntries'; import { processStreamRecordsInteractor } from './processStreamRecordsInteractor'; +import { processUserCaseNoteEntries } from './processUserCaseNoteEntries'; import { processWorkItemEntries } from './processWorkItemEntries'; describe('processStreamRecordsInteractor', () => { @@ -25,6 +27,7 @@ describe('processStreamRecordsInteractor', () => { (processWorkItemEntries as jest.Mock).mockResolvedValue([]); (processMessageEntries as jest.Mock).mockResolvedValue([]); (processPractitionerMappingEntries as jest.Mock).mockResolvedValue([]); + (processUserCaseNoteEntries as jest.Mock).mockResolvedValue([]); (processOtherEntries as jest.Mock).mockResolvedValue([]); (partitionRecords as jest.Mock).mockReturnValue({ @@ -34,6 +37,7 @@ describe('processStreamRecordsInteractor', () => { otherRecords: [], privatePractitionerMappingRecords: [], removeRecords: [], + userCaseNoteRecords: [], workItemRecords: [], }); }); @@ -60,6 +64,7 @@ describe('processStreamRecordsInteractor', () => { expect(processDocketEntries).not.toHaveBeenCalled(); expect(processWorkItemEntries).not.toHaveBeenCalled(); expect(processMessageEntries).not.toHaveBeenCalled(); + expect(processUserCaseNoteEntries).not.toHaveBeenCalled(); expect(processPractitionerMappingEntries).not.toHaveBeenCalled(); expect(processOtherEntries).not.toHaveBeenCalled(); expect(applicationContext.logger.error).toHaveBeenCalledTimes(2); diff --git a/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.ts b/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.ts index 42784a7475e..62bb172816d 100644 --- a/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.ts +++ b/web-api/src/business/useCases/processStreamRecords/processStreamRecordsInteractor.ts @@ -7,6 +7,7 @@ import { processMessageEntries } from './processMessageEntries'; import { processOtherEntries } from './processOtherEntries'; import { processPractitionerMappingEntries } from './processPractitionerMappingEntries'; import { processRemoveEntries } from './processRemoveEntries'; +import { processUserCaseNoteEntries } from '@web-api/business/useCases/processStreamRecords/processUserCaseNoteEntries'; import { processWorkItemEntries } from './processWorkItemEntries'; import type { DynamoDBRecord } from 'aws-lambda'; @@ -22,6 +23,7 @@ export const processStreamRecordsInteractor = async ( otherRecords, practitionerMappingRecords, removeRecords, + userCaseNoteRecords, workItemRecords, } = partitionRecords(recordsToProcess); @@ -75,6 +77,19 @@ export const processStreamRecordsInteractor = async ( throw err; }); + await processUserCaseNoteEntries({ + applicationContext, + userCaseNoteRecords, + }).catch(err => { + applicationContext.logger.error( + 'failed to process userCaseNote records', + { + err, + }, + ); + throw err; + }); + await processPractitionerMappingEntries({ applicationContext, practitionerMappingRecords, diff --git a/web-api/src/business/useCases/processStreamRecords/processStreamUtilities.ts b/web-api/src/business/useCases/processStreamRecords/processStreamUtilities.ts index 16efeb1abb5..1b5cb1db0b1 100644 --- a/web-api/src/business/useCases/processStreamRecords/processStreamUtilities.ts +++ b/web-api/src/business/useCases/processStreamRecords/processStreamUtilities.ts @@ -48,8 +48,15 @@ export const partitionRecords = ( record.dynamodb.NewImage.entityName.S === 'Message', ); - const [completionMarkers, otherRecords] = partition( + const [userCaseNoteRecords, nonUserCaseNoteRecords] = partition( nonMessageRecords, + record => + record.dynamodb?.NewImage?.entityName && + record.dynamodb.NewImage.entityName.S === 'UserCaseNote', + ); + + const [completionMarkers, otherRecords] = partition( + nonUserCaseNoteRecords, record => record.dynamodb?.NewImage?.entityName && record.dynamodb.NewImage.entityName.S === 'CompletionMarker', @@ -63,6 +70,7 @@ export const partitionRecords = ( otherRecords, practitionerMappingRecords, removeRecords, + userCaseNoteRecords, workItemRecords, }; }; diff --git a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts new file mode 100644 index 00000000000..018d5c48f57 --- /dev/null +++ b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts @@ -0,0 +1,46 @@ +import '@web-api/persistence/postgres/messages/mocks.jest'; +import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; +import { processMessageEntries } from './processMessageEntries'; +import { upsertUserCaseNotes } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNotes'; + +jest.mock('@web-api/persistence/postgres/messages/upsertMessages'); + +describe('processUserCaseNoteEntries', () => { + beforeEach(() => { + (upsertUserCaseNotes as jest.Mock).mockResolvedValue(undefined); + }); + + it('should attempt to store the user case notes using the upsert method', async () => { + const mockUserCaseNoteRecord = { + dynamodb: { + NewImage: { + docketNumber: { + S: '104-20', + }, + entityName: { + S: 'UserCaseNote', + }, + notes: { + S: 'Test', + }, + pk: { + S: 'user-case-note|104-20', + }, + sk: { + S: 'user|c4a1a9da-ac90-40f1-8d4d-d494c219cbbe', + }, + userId: { + S: 'c4a1a9da-ac90-40f1-8d4d-d494c219cbbe', + }, + }, + }, + }; + + await processMessageEntries({ + applicationContext, + messageRecords: [mockUserCaseNoteRecord], + }); + + expect(upsertUserCaseNotes).toHaveBeenCalled(); + }); +}); diff --git a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.ts b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.ts new file mode 100644 index 00000000000..7cc15c27528 --- /dev/null +++ b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.ts @@ -0,0 +1,26 @@ +import { RawUserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; +import { unmarshall } from '@aws-sdk/util-dynamodb'; +import { upsertUserCaseNotes } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNotes'; +import type { ServerApplicationContext } from '@web-api/applicationContext'; + +export const processUserCaseNoteEntries = async ({ + applicationContext, + userCaseNoteRecords, +}: { + applicationContext: ServerApplicationContext; + userCaseNoteRecords: any[]; +}) => { + if (!userCaseNoteRecords.length) return; + + applicationContext.logger.debug( + `going to index ${userCaseNoteRecords.length} userCaseNote records`, + ); + + await upsertUserCaseNotes( + userCaseNoteRecords.map(userCaseNoteRecord => { + return unmarshall( + userCaseNoteRecord.dynamodb.NewImage, + ) as RawUserCaseNote; + }), + ); +}; diff --git a/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts b/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts index 17a4a7e4d4c..b9992495a06 100644 --- a/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts +++ b/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts @@ -19,3 +19,8 @@ jest.mock( '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote.ts', () => mockFactory('updateUserCaseNote'), ); + +jest.mock( + '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNotes.ts', + () => mockFactory('upsertUserCaseNotes'), +); diff --git a/web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNotes.ts b/web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNotes.ts new file mode 100644 index 00000000000..dd951929521 --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNotes.ts @@ -0,0 +1,26 @@ +import { RawUserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; +import { getDbWriter } from '@web-api/database'; + +export const upsertUserCaseNotes = async (userCaseNotes: RawUserCaseNote[]) => { + if (userCaseNotes.length === 0) return; + + const userCaseNotesToUpsert = userCaseNotes.map(rawUserCaseNote => ({ + docketNumber: rawUserCaseNote.docketNumber, + notes: rawUserCaseNote.notes, + userId: rawUserCaseNote.userId, + })); + + await getDbWriter(writer => + writer + .insertInto('dwUserCaseNote') + .values(userCaseNotesToUpsert) + .onConflict(oc => + oc.columns(['docketNumber', 'userId']).doUpdateSet(c => { + return { + notes: c.ref('excluded.notes'), + }; + }), + ) + .execute(), + ); +}; From b09299f6b0133c2946c51e2867354bdcd209087b Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 4 Oct 2024 11:00:38 -0600 Subject: [PATCH 03/14] 10492: quick fix --- .../processStreamRecords/processUserCaseNoteEntries.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts index 018d5c48f57..526b1b27cdd 100644 --- a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts +++ b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts @@ -1,6 +1,6 @@ import '@web-api/persistence/postgres/messages/mocks.jest'; import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; -import { processMessageEntries } from './processMessageEntries'; +import { processUserCaseNoteEntries } from '@web-api/business/useCases/processStreamRecords/processUserCaseNoteEntries'; import { upsertUserCaseNotes } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNotes'; jest.mock('@web-api/persistence/postgres/messages/upsertMessages'); @@ -36,9 +36,9 @@ describe('processUserCaseNoteEntries', () => { }, }; - await processMessageEntries({ + await processUserCaseNoteEntries({ applicationContext, - messageRecords: [mockUserCaseNoteRecord], + userCaseNoteRecords: [mockUserCaseNoteRecord], }); expect(upsertUserCaseNotes).toHaveBeenCalled(); From 1313dfe654f97cc9b77b55dbb03db7032f8a7669 Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 4 Oct 2024 11:01:54 -0600 Subject: [PATCH 04/14] 10492: quick fix --- .../processStreamRecords/processUserCaseNoteEntries.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts index 526b1b27cdd..1ed90648748 100644 --- a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts +++ b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts @@ -3,8 +3,6 @@ import { applicationContext } from '../../../../../shared/src/business/test/crea import { processUserCaseNoteEntries } from '@web-api/business/useCases/processStreamRecords/processUserCaseNoteEntries'; import { upsertUserCaseNotes } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNotes'; -jest.mock('@web-api/persistence/postgres/messages/upsertMessages'); - describe('processUserCaseNoteEntries', () => { beforeEach(() => { (upsertUserCaseNotes as jest.Mock).mockResolvedValue(undefined); From 7b5cb98083352a4b611d0eb66db3956969a52589 Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 4 Oct 2024 11:19:21 -0600 Subject: [PATCH 05/14] 10492: fix api tests --- .../deleteUserCaseNoteInteractor.test.ts | 13 ++++++----- .../getUserCaseNoteInteractor.test.ts | 23 +++++++------------ .../updateUserCaseNoteInteractor.test.ts | 15 ++++++------ 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts index a54b34e0d96..38d94ce23f8 100644 --- a/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/deleteUserCaseNoteInteractor.test.ts @@ -5,10 +5,13 @@ import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { User } from '../../../../../shared/src/business/entities/User'; import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; import { deleteUserCaseNoteInteractor } from './deleteUserCaseNoteInteractor'; +import { deleteUserCaseNote as deleteUserCaseNoteMock } from '@web-api/persistence/postgres/userCaseNotes/deleteUserCaseNote'; import { mockJudgeUser } from '@shared/test/mockAuthUsers'; import { omit } from 'lodash'; describe('deleteUserCaseNoteInteractor', () => { + const deleteUserCaseNote = deleteUserCaseNoteMock as jest.Mock; + it('throws an error if the user is not valid or authorized', async () => { let user = {} as UnknownAuthUser; @@ -34,7 +37,7 @@ describe('deleteUserCaseNoteInteractor', () => { applicationContext .getPersistenceGateway() .getUserById.mockReturnValue(mockUser); - applicationContext.getPersistenceGateway().deleteUserCaseNote = v => v; + deleteUserCaseNote.mockImplementation(v => v); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue({ @@ -61,7 +64,6 @@ describe('deleteUserCaseNoteInteractor', () => { applicationContext .getPersistenceGateway() .getUserById.mockReturnValue(mockUser); - applicationContext.getPersistenceGateway().deleteUserCaseNote = jest.fn(); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(null); @@ -73,9 +75,8 @@ describe('deleteUserCaseNoteInteractor', () => { omit(mockUser, 'section'), ); - expect( - applicationContext.getPersistenceGateway().deleteUserCaseNote.mock - .calls[0][0].userId, - ).toEqual(mockJudgeUser.userId); + expect(deleteUserCaseNote.mock.calls[0][0].userId).toEqual( + mockJudgeUser.userId, + ); }); }); diff --git a/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts index a5845bcb286..4d441df7265 100644 --- a/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/getUserCaseNoteInteractor.test.ts @@ -5,6 +5,7 @@ import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { User } from '../../../../../shared/src/business/entities/User'; import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; import { getUserCaseNoteInteractor } from './getUserCaseNoteInteractor'; +import { getUserCaseNote as getUserCaseNoteMock } from '@web-api/persistence/postgres/userCaseNotes/getUserCaseNote'; import { mockJudgeUser } from '@shared/test/mockAuthUsers'; import { omit } from 'lodash'; @@ -20,13 +21,13 @@ describe('Get case note', () => { userId: 'unauthorizedUser', } as unknown as UnknownAuthUser; + const getUserCaseNote = getUserCaseNoteMock as jest.Mock; + it('throws error if user is unauthorized', async () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => new User(mockUnauthorizedUser)); - applicationContext - .getPersistenceGateway() - .getUserCaseNote.mockReturnValue({}); + getUserCaseNote.mockReturnValue({}); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(null); @@ -46,9 +47,7 @@ describe('Get case note', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => new User(mockJudgeUser)); - applicationContext - .getPersistenceGateway() - .getUserCaseNote.mockResolvedValue(omit(MOCK_NOTE, 'userId')); + getUserCaseNote.mockResolvedValue(omit(MOCK_NOTE, 'userId')); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(mockJudgeUser); @@ -68,9 +67,7 @@ describe('Get case note', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => new User(mockJudgeUser)); - applicationContext - .getPersistenceGateway() - .getUserCaseNote.mockResolvedValue(MOCK_NOTE); + getUserCaseNote.mockResolvedValue(MOCK_NOTE); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(mockJudgeUser); @@ -90,9 +87,7 @@ describe('Get case note', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => new User(mockJudgeUser)); - applicationContext - .getPersistenceGateway() - .getUserCaseNote.mockResolvedValue(MOCK_NOTE); + getUserCaseNote.mockResolvedValue(MOCK_NOTE); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(null); @@ -115,9 +110,7 @@ describe('Get case note', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => new User(mockJudgeUser)); - applicationContext - .getPersistenceGateway() - .getUserCaseNote.mockReturnValue(null); + getUserCaseNote.mockReturnValue(null); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(mockJudgeUser); diff --git a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts index 47d86525197..a4db9894ba0 100644 --- a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts @@ -6,6 +6,7 @@ import { applicationContext } from '../../../../../shared/src/business/test/crea import { mockJudgeUser } from '@shared/test/mockAuthUsers'; import { omit } from 'lodash'; import { updateUserCaseNoteInteractor } from './updateUserCaseNoteInteractor'; +import { updateUserCaseNote as updateUserCaseNoteMock } from '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote'; describe('updateUserCaseNoteInteractor', () => { const mockCaseNote = { @@ -14,6 +15,8 @@ describe('updateUserCaseNoteInteractor', () => { userId: '6805d1ab-18d0-43ec-bafb-654e83405416', }; + const updateUserCaseNote = updateUserCaseNoteMock as jest.Mock; + it('throws an error if the user is not valid or authorized', async () => { await expect( updateUserCaseNoteInteractor( @@ -35,9 +38,7 @@ describe('updateUserCaseNoteInteractor', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => mockUser); - applicationContext - .getPersistenceGateway() - .updateUserCaseNote.mockImplementation(v => v.caseNoteToUpdate); + updateUserCaseNote.mockImplementation(v => v.caseNoteToUpdate); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue({ @@ -68,7 +69,6 @@ describe('updateUserCaseNoteInteractor', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => mockUser); - applicationContext.getPersistenceGateway().updateUserCaseNote = jest.fn(); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(null); @@ -82,9 +82,8 @@ describe('updateUserCaseNoteInteractor', () => { omit(mockUser, 'section'), ); - expect( - applicationContext.getPersistenceGateway().updateUserCaseNote.mock - .calls[0][0].caseNoteToUpdate.userId, - ).toEqual(userIdToExpect); + expect(updateUserCaseNote.mock.calls[0][0].caseNoteToUpdate.userId).toEqual( + userIdToExpect, + ); }); }); From 78e0cf3f32aa98f0605c1056de1df899d14c3ee7 Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 4 Oct 2024 11:26:52 -0600 Subject: [PATCH 06/14] 10492: add test user case note --- web-api/storage/fixtures/seed/efcms-local.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web-api/storage/fixtures/seed/efcms-local.json b/web-api/storage/fixtures/seed/efcms-local.json index f873badc8d6..397b1a523ee 100644 --- a/web-api/storage/fixtures/seed/efcms-local.json +++ b/web-api/storage/fixtures/seed/efcms-local.json @@ -58611,5 +58611,13 @@ "pk": "user|f0a1e52a-876f-4c03-853c-f66e407e5a1e", "userId": "f0a1e52a-876f-4c03-853c-f66e407e5a1e", "email": "trialclerk@example.com" + }, + { + "pk": "user-case-note|107-19", + "sk": "user|dabbad00-18d0-43ec-bafb-654e83405416", + "docketNumber": "107-19", + "entityName": "UserCaseNote", + "notes": "Test", + "userId": "dabbad00-18d0-43ec-bafb-654e83405416" } ] From 485c741991748cc9b24aead1275b097a64ca53aa Mon Sep 17 00:00:00 2001 From: Kaitlyn Swann Date: Fri, 4 Oct 2024 11:39:57 -0600 Subject: [PATCH 07/14] 10492: fix api tests --- .../getUserCaseNoteForCasesInteractor.test.ts | 21 ++++++++++--------- .../processUserCaseNoteEntries.test.ts | 2 +- .../messages/getMessagesByDocketNumber.ts | 2 -- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts b/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts index 322b49c3597..7f07f74b45f 100644 --- a/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/getUserCaseNoteForCasesInteractor.test.ts @@ -2,8 +2,10 @@ import '@web-api/persistence/postgres/userCaseNotes/mocks.jest'; import { MOCK_CASE } from '../../../../../shared/src/test/mockCase'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; +import { UserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; import { getUserCaseNoteForCasesInteractor } from './getUserCaseNoteForCasesInteractor'; +import { getUserCaseNoteForCases as getUserCaseNoteForCasesMock } from '@web-api/persistence/postgres/userCaseNotes/getUserCaseNoteForCases'; import { mockJudgeUser } from '@shared/test/mockAuthUsers'; import { omit } from 'lodash'; @@ -22,15 +24,15 @@ describe('getUserCaseNoteForCasesInteractor', () => { section: 'colvinChambers', } as UnknownAuthUser; + const getUserCaseNoteForCases = getUserCaseNoteForCasesMock as jest.Mock; + beforeEach(() => { mockCurrentUser = mockJudge; mockNote = MOCK_NOTE; applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => mockCurrentUser); - applicationContext - .getPersistenceGateway() - .getUserCaseNoteForCases.mockResolvedValue([mockNote]); + getUserCaseNoteForCases.mockResolvedValue([new UserCaseNote(mockNote)]); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue(mockJudge); @@ -53,9 +55,9 @@ describe('getUserCaseNoteForCasesInteractor', () => { }); it('throws an error if the entity returned from persistence is invalid', async () => { - applicationContext - .getPersistenceGateway() - .getUserCaseNoteForCases.mockResolvedValue([omit(MOCK_NOTE, 'userId')]); + getUserCaseNoteForCases.mockResolvedValue([ + new UserCaseNote([omit(MOCK_NOTE, 'userId')]), + ]); await expect( getUserCaseNoteForCasesInteractor( @@ -101,9 +103,8 @@ describe('getUserCaseNoteForCasesInteractor', () => { omit(mockUser, 'section'), ); - expect( - applicationContext.getPersistenceGateway().getUserCaseNoteForCases.mock - .calls[0][0].userId, - ).toEqual(userIdToExpect); + expect(getUserCaseNoteForCases.mock.calls[0][0].userId).toEqual( + userIdToExpect, + ); }); }); diff --git a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts index 1ed90648748..b62ef80ae62 100644 --- a/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts +++ b/web-api/src/business/useCases/processStreamRecords/processUserCaseNoteEntries.test.ts @@ -1,4 +1,4 @@ -import '@web-api/persistence/postgres/messages/mocks.jest'; +import '@web-api/persistence/postgres/userCaseNotes/mocks.jest'; import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; import { processUserCaseNoteEntries } from '@web-api/business/useCases/processStreamRecords/processUserCaseNoteEntries'; import { upsertUserCaseNotes } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNotes'; diff --git a/web-api/src/persistence/postgres/messages/getMessagesByDocketNumber.ts b/web-api/src/persistence/postgres/messages/getMessagesByDocketNumber.ts index 9a3d29c5338..bdf7702fb6c 100644 --- a/web-api/src/persistence/postgres/messages/getMessagesByDocketNumber.ts +++ b/web-api/src/persistence/postgres/messages/getMessagesByDocketNumber.ts @@ -17,7 +17,5 @@ export const getMessagesByDocketNumber = async ({ .execute(), ); - console.log('*** messages', messages); - return messages.map(message => messageResultEntity(message)); }; From 3147b33d663875f494cc85872cea34b2805d1a36 Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Wed, 6 Nov 2024 10:53:15 -0500 Subject: [PATCH 08/14] 10492-dxox: rename updateUserCaseNote.ts to upsertUserCaseNote.ts --- types/TEntity.d.ts | 6 ----- .../updateUserCaseNoteInteractor.test.ts | 8 +++---- .../caseNote/updateUserCaseNoteInteractor.ts | 12 ++++------ .../postgres/userCaseNotes/mocks.jest.ts | 4 ++-- .../userCaseNotes/updateUserCaseNote.ts | 23 ------------------ .../userCaseNotes/upsertUserCaseNote.ts | 24 +++++++++++++++++++ 6 files changed, 35 insertions(+), 42 deletions(-) delete mode 100644 web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts create mode 100644 web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNote.ts diff --git a/types/TEntity.d.ts b/types/TEntity.d.ts index 83013e86ea9..cffd14bcce3 100644 --- a/types/TEntity.d.ts +++ b/types/TEntity.d.ts @@ -27,12 +27,6 @@ type TPetitioner = { hasConsentedToEService?: boolean; }; -type TCaseNote = { - userId: string; - docketNumber: string; - notes: string; -}; - interface IValidateRawCollection { (collection: I[], options: { applicationContext: IApplicationContext }): I[]; } diff --git a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts index a4db9894ba0..166fb423a6c 100644 --- a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts @@ -6,7 +6,7 @@ import { applicationContext } from '../../../../../shared/src/business/test/crea import { mockJudgeUser } from '@shared/test/mockAuthUsers'; import { omit } from 'lodash'; import { updateUserCaseNoteInteractor } from './updateUserCaseNoteInteractor'; -import { updateUserCaseNote as updateUserCaseNoteMock } from '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote'; +import { upsertUserCaseNote as upsertUserCaseNoteMock } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNote'; describe('updateUserCaseNoteInteractor', () => { const mockCaseNote = { @@ -15,7 +15,7 @@ describe('updateUserCaseNoteInteractor', () => { userId: '6805d1ab-18d0-43ec-bafb-654e83405416', }; - const updateUserCaseNote = updateUserCaseNoteMock as jest.Mock; + const upsertUserCaseNote = upsertUserCaseNoteMock as jest.Mock; it('throws an error if the user is not valid or authorized', async () => { await expect( @@ -38,7 +38,7 @@ describe('updateUserCaseNoteInteractor', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => mockUser); - updateUserCaseNote.mockImplementation(v => v.caseNoteToUpdate); + upsertUserCaseNote.mockImplementation(v => v.caseNoteToUpdate); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue({ @@ -82,7 +82,7 @@ describe('updateUserCaseNoteInteractor', () => { omit(mockUser, 'section'), ); - expect(updateUserCaseNote.mock.calls[0][0].caseNoteToUpdate.userId).toEqual( + expect(upsertUserCaseNote.mock.calls[0][0].caseNoteToUpdate.userId).toEqual( userIdToExpect, ); }); diff --git a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts index da81f2f1b5e..e7b996fa74e 100644 --- a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts +++ b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.ts @@ -5,7 +5,7 @@ import { import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; import { UserCaseNote } from '../../../../../shared/src/business/entities/notes/UserCaseNote'; -import { updateUserCaseNote } from '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote'; +import { upsertUserCaseNote } from '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNote'; export const updateUserCaseNoteInteractor = async ( applicationContext, @@ -28,13 +28,11 @@ export const updateUserCaseNoteInteractor = async ( docketNumber, notes, userId, - }); - - const caseNoteToUpdate = caseNoteEntity.validate().toRawObject(); + }).validate(); - await updateUserCaseNote({ - caseNoteToUpdate, + await upsertUserCaseNote({ + caseNoteToUpsert: caseNoteEntity, }); - return caseNoteToUpdate; + return caseNoteEntity; }; diff --git a/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts b/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts index b9992495a06..e13040c7cd9 100644 --- a/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts +++ b/web-api/src/persistence/postgres/userCaseNotes/mocks.jest.ts @@ -16,8 +16,8 @@ jest.mock( ); jest.mock( - '@web-api/persistence/postgres/userCaseNotes/updateUserCaseNote.ts', - () => mockFactory('updateUserCaseNote'), + '@web-api/persistence/postgres/userCaseNotes/upsertUserCaseNote.ts', + () => mockFactory('upsertUserCaseNote'), ); jest.mock( diff --git a/web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts b/web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts deleted file mode 100644 index 4919e05f31e..00000000000 --- a/web-api/src/persistence/postgres/userCaseNotes/updateUserCaseNote.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getDbWriter } from '@web-api/database'; - -export const updateUserCaseNote = async ({ - caseNoteToUpdate, -}: { - caseNoteToUpdate: TCaseNote; -}) => { - await getDbWriter(writer => - writer - .insertInto('dwUserCaseNote') - .values({ - docketNumber: caseNoteToUpdate.docketNumber, - notes: caseNoteToUpdate.notes, - userId: caseNoteToUpdate.userId, - }) - .onConflict(oc => - oc.columns(['docketNumber', 'userId']).doUpdateSet({ - notes: caseNoteToUpdate.notes, - }), - ) - .execute(), - ); -}; diff --git a/web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNote.ts b/web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNote.ts new file mode 100644 index 00000000000..a038d5615fb --- /dev/null +++ b/web-api/src/persistence/postgres/userCaseNotes/upsertUserCaseNote.ts @@ -0,0 +1,24 @@ +import { UserCaseNote } from '@shared/business/entities/notes/UserCaseNote'; +import { getDbWriter } from '@web-api/database'; + +export const upsertUserCaseNote = async ({ + caseNoteToUpsert, +}: { + caseNoteToUpsert: UserCaseNote; +}) => { + await getDbWriter(writer => + writer + .insertInto('dwUserCaseNote') + .values({ + docketNumber: caseNoteToUpsert.docketNumber, + notes: caseNoteToUpsert.notes, + userId: caseNoteToUpsert.userId, + }) + .onConflict(oc => + oc.columns(['docketNumber', 'userId']).doUpdateSet({ + notes: caseNoteToUpsert.notes, + }), + ) + .execute(), + ); +}; From 0a3c9816a9cf4851f6cc4362f56fa7e8b719824c Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Wed, 6 Nov 2024 11:05:38 -0500 Subject: [PATCH 09/14] 10492-dxox: fix missed rename in updateUserCaseNoteInteractor --- .../useCases/caseNote/updateUserCaseNoteInteractor.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts index 166fb423a6c..3e1e541f59c 100644 --- a/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts +++ b/web-api/src/business/useCases/caseNote/updateUserCaseNoteInteractor.test.ts @@ -38,7 +38,7 @@ describe('updateUserCaseNoteInteractor', () => { applicationContext .getPersistenceGateway() .getUserById.mockImplementation(() => mockUser); - upsertUserCaseNote.mockImplementation(v => v.caseNoteToUpdate); + upsertUserCaseNote.mockImplementation(v => v.caseNoteToUpsert); applicationContext .getUseCaseHelpers() .getJudgeInSectionHelper.mockReturnValue({ @@ -82,7 +82,7 @@ describe('updateUserCaseNoteInteractor', () => { omit(mockUser, 'section'), ); - expect(upsertUserCaseNote.mock.calls[0][0].caseNoteToUpdate.userId).toEqual( + expect(upsertUserCaseNote.mock.calls[0][0].caseNoteToUpsert.userId).toEqual( userIdToExpect, ); }); From dd0f4ff75e8bb44f2851a65647fdf05b265421a6 Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Wed, 20 Nov 2024 10:29:11 -0500 Subject: [PATCH 10/14] 10492-dxox: add delete script --- .../batch-delete-dynamo-items.ts | 63 +++++++++++++++++++ .../postgres-migration/delete-case-notes | 62 ++++++++++++++++++ .../delete-messages.ts | 2 +- 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 scripts/run-once-scripts/postgres-migration/batch-delete-dynamo-items.ts create mode 100644 scripts/run-once-scripts/postgres-migration/delete-case-notes rename scripts/run-once-scripts/{ => postgres-migration}/delete-messages.ts (98%) diff --git a/scripts/run-once-scripts/postgres-migration/batch-delete-dynamo-items.ts b/scripts/run-once-scripts/postgres-migration/batch-delete-dynamo-items.ts new file mode 100644 index 00000000000..60284d20ad4 --- /dev/null +++ b/scripts/run-once-scripts/postgres-migration/batch-delete-dynamo-items.ts @@ -0,0 +1,63 @@ +import { + BatchWriteCommand, + DynamoDBDocumentClient, +} from '@aws-sdk/lib-dynamodb'; + +export async function batchDeleteDynamoItems( + itemsToDelete: { DeleteRequest: { Key: { pk: string; sk: string } } }[], + client: DynamoDBDocumentClient, + tableNameInput: string, +): Promise { + const BATCH_SIZE = 25; + const RETRY_DELAY_MS = 5000; // Set the delay between retries (in milliseconds) + let totalItemsDeleted = 0; + + for (let i = 0; i < itemsToDelete.length; i += BATCH_SIZE) { + const batch = itemsToDelete.slice(i, i + BATCH_SIZE); + + const batchWriteParams = { + RequestItems: { + [tableNameInput]: batch, + }, + }; + + try { + let unprocessedItems: any[] = batch; + let retryCount = 0; + const MAX_RETRIES = 5; + + // Retry logic for unprocessed items + while (unprocessedItems.length > 0 && retryCount < MAX_RETRIES) { + const response = await client.send( + new BatchWriteCommand(batchWriteParams), + ); + + totalItemsDeleted += + unprocessedItems.length - + (response.UnprocessedItems?.[tableNameInput]?.length || 0); + + unprocessedItems = response.UnprocessedItems?.[tableNameInput] ?? []; + + if (unprocessedItems.length > 0) { + console.log( + `Retrying unprocessed items: ${unprocessedItems.length}, attempt ${retryCount + 1}`, + ); + batchWriteParams.RequestItems[tableNameInput] = unprocessedItems; + retryCount++; + + // Add delay before the next retry + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); + } + } + + if (unprocessedItems.length > 0) { + console.error( + `Failed to delete ${unprocessedItems.length} items after ${MAX_RETRIES} retries.`, + ); + } + } catch (error) { + console.error('Error in batch delete:', error); + } + } + return totalItemsDeleted; +} diff --git a/scripts/run-once-scripts/postgres-migration/delete-case-notes b/scripts/run-once-scripts/postgres-migration/delete-case-notes new file mode 100644 index 00000000000..b50683495cc --- /dev/null +++ b/scripts/run-once-scripts/postgres-migration/delete-case-notes @@ -0,0 +1,62 @@ +/** + * HOW TO RUN + * + * TABLE_NAME=testing npx ts-node --transpileOnly scripts/run-once-scripts/postgres-migration/delete-case-notes.ts + */ + +import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { requireEnvVars } from '../../../shared/admin-tools/util'; +import { getDbReader } from '../../../web-api/src/database'; +import { isEmpty } from 'lodash'; +import { batchDeleteDynamoItems } from './batch-delete-dynamo-items'; + +const caseUserNotesPageSize = 10000; +const dynamoDbClient = new DynamoDBClient({ region: 'us-east-1' }); +const dynamoDbDocClient = DynamoDBDocumentClient.from(dynamoDbClient); + +requireEnvVars(['TABLE_NAME']); + +const tableNameInput = process.env.TABLE_NAME!; + +const getCaseNotesToDelete = async (offset: number) => { + const caseNotes = await getDbReader(reader => + reader + .selectFrom('dwUserCaseNote') + .select(['docketNumber', 'userId']) + .orderBy('docketNumber') + .limit(caseUserNotesPageSize) + .offset(offset) + .execute(), + ); + return caseNotes; +}; + +let totalItemsDeleted = 0; + +async function main() { + let offset = 0; + let caseNotesToDelete = await getCaseNotesToDelete(offset); + + while (!isEmpty(caseNotesToDelete)) { + const dynamoItemsToDelete = caseNotesToDelete.map(cd => ({ + DeleteRequest: { + Key: { + pk: `user-case-note|${cd.docketNumber}`, + sk: `user${cd.userId}`, + }, + }, + })); + totalItemsDeleted += await batchDeleteDynamoItems( + dynamoItemsToDelete, + dynamoDbDocClient, + tableNameInput, + ); + console.log(`Total case notes deleted so far: ${totalItemsDeleted}`); + offset += caseUserNotesPageSize; + caseNotesToDelete = await getCaseNotesToDelete(offset); + } + console.log('Done deleting case notes from Dynamo'); +} + +main().catch(console.error); diff --git a/scripts/run-once-scripts/delete-messages.ts b/scripts/run-once-scripts/postgres-migration/delete-messages.ts similarity index 98% rename from scripts/run-once-scripts/delete-messages.ts rename to scripts/run-once-scripts/postgres-migration/delete-messages.ts index 6ed1a66b356..cc022e06f85 100644 --- a/scripts/run-once-scripts/delete-messages.ts +++ b/scripts/run-once-scripts/postgres-migration/delete-messages.ts @@ -10,7 +10,7 @@ import { ScanCommand, } from '@aws-sdk/lib-dynamodb'; import { DynamoDBClient, ScanCommandInput } from '@aws-sdk/client-dynamodb'; -import { requireEnvVars } from '../../shared/admin-tools/util'; +import { requireEnvVars } from '../../../shared/admin-tools/util'; requireEnvVars(['TABLE_NAME']); From 75884f52c8e40826f67cfe2c63a7b34b182355a6 Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Wed, 20 Nov 2024 11:59:14 -0500 Subject: [PATCH 11/14] 10492-dxox: fix typo in delete-case-notes --- .../run-once-scripts/postgres-migration/delete-case-notes | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/run-once-scripts/postgres-migration/delete-case-notes b/scripts/run-once-scripts/postgres-migration/delete-case-notes index b50683495cc..b237849f1be 100644 --- a/scripts/run-once-scripts/postgres-migration/delete-case-notes +++ b/scripts/run-once-scripts/postgres-migration/delete-case-notes @@ -24,7 +24,7 @@ const getCaseNotesToDelete = async (offset: number) => { reader .selectFrom('dwUserCaseNote') .select(['docketNumber', 'userId']) - .orderBy('docketNumber') + .orderBy('userId') .limit(caseUserNotesPageSize) .offset(offset) .execute(), @@ -39,11 +39,11 @@ async function main() { let caseNotesToDelete = await getCaseNotesToDelete(offset); while (!isEmpty(caseNotesToDelete)) { - const dynamoItemsToDelete = caseNotesToDelete.map(cd => ({ + const dynamoItemsToDelete = caseNotesToDelete.map(c => ({ DeleteRequest: { Key: { - pk: `user-case-note|${cd.docketNumber}`, - sk: `user${cd.userId}`, + pk: `user-case-note|${c.docketNumber}`, + sk: `user|${c.userId}`, }, }, })); From 38417e8589ebb05e04805d15abb06f57395aa7c0 Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Wed, 20 Nov 2024 13:01:36 -0500 Subject: [PATCH 12/14] 10492-dxox: switch to podman compose in run-local.sh --- run-local.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-local.sh b/run-local.sh index 27279c6f2d4..c7a278376fc 100755 --- a/run-local.sh +++ b/run-local.sh @@ -6,10 +6,10 @@ if [[ -z "$CI" ]]; then echo "Stopping postgres in case it's already running" - docker-compose -f web-api/src/persistence/postgres/docker-compose.yml down || true + podman compose -f web-api/src/persistence/postgres/docker-compose.yml down || true echo "Starting postgres" - docker-compose -f web-api/src/persistence/postgres/docker-compose.yml up -d || { echo "Failed to start Postgres containers"; exit 1; } + podman compose -f web-api/src/persistence/postgres/docker-compose.yml up -d || { echo "Failed to start Postgres containers"; exit 1; } echo "Stopping dynamodb in case it's already running" pkill -f DynamoDBLocal From e5a27491f33de76ad1b75d7df7595bf7260efa72 Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Tue, 3 Dec 2024 14:06:07 -0500 Subject: [PATCH 13/14] 10492-dxox: fix delete script to avoid unstable sort and to get RDS connection to work properly --- .../{delete-case-notes => delete-case-notes.ts} | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) rename scripts/run-once-scripts/postgres-migration/{delete-case-notes => delete-case-notes.ts} (79%) diff --git a/scripts/run-once-scripts/postgres-migration/delete-case-notes b/scripts/run-once-scripts/postgres-migration/delete-case-notes.ts similarity index 79% rename from scripts/run-once-scripts/postgres-migration/delete-case-notes rename to scripts/run-once-scripts/postgres-migration/delete-case-notes.ts index b237849f1be..3fb02442769 100644 --- a/scripts/run-once-scripts/postgres-migration/delete-case-notes +++ b/scripts/run-once-scripts/postgres-migration/delete-case-notes.ts @@ -1,30 +1,28 @@ /** * HOW TO RUN - * - * TABLE_NAME=testing npx ts-node --transpileOnly scripts/run-once-scripts/postgres-migration/delete-case-notes.ts + * npx ts-node --transpileOnly scripts/run-once-scripts/postgres-migration/delete-case-notes.ts */ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { requireEnvVars } from '../../../shared/admin-tools/util'; import { getDbReader } from '../../../web-api/src/database'; import { isEmpty } from 'lodash'; import { batchDeleteDynamoItems } from './batch-delete-dynamo-items'; +import { environment } from '../../../web-api/src/environment'; const caseUserNotesPageSize = 10000; const dynamoDbClient = new DynamoDBClient({ region: 'us-east-1' }); const dynamoDbDocClient = DynamoDBDocumentClient.from(dynamoDbClient); -requireEnvVars(['TABLE_NAME']); - -const tableNameInput = process.env.TABLE_NAME!; +// We set the environment as 'production' (= "a deployed environment") to get the RDS connection to work properly +environment.nodeEnv = 'production'; const getCaseNotesToDelete = async (offset: number) => { const caseNotes = await getDbReader(reader => reader .selectFrom('dwUserCaseNote') .select(['docketNumber', 'userId']) - .orderBy('userId') + .orderBy(['docketNumber', 'userId']) .limit(caseUserNotesPageSize) .offset(offset) .execute(), @@ -50,7 +48,7 @@ async function main() { totalItemsDeleted += await batchDeleteDynamoItems( dynamoItemsToDelete, dynamoDbDocClient, - tableNameInput, + environment.dynamoDbTableName, ); console.log(`Total case notes deleted so far: ${totalItemsDeleted}`); offset += caseUserNotesPageSize; From 13bf9f3efa139f3c78a7fdfa97e7495201e6fdba Mon Sep 17 00:00:00 2001 From: Christopher Bisom Date: Thu, 12 Dec 2024 15:58:34 -0500 Subject: [PATCH 14/14] 10492-dxox: rename 0004 to 0003 --- .../{0004-add-user-case-notes.ts => 0003-add-user-case-notes.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web-api/src/persistence/postgres/utils/migrate/migrations/{0004-add-user-case-notes.ts => 0003-add-user-case-notes.ts} (100%) diff --git a/web-api/src/persistence/postgres/utils/migrate/migrations/0004-add-user-case-notes.ts b/web-api/src/persistence/postgres/utils/migrate/migrations/0003-add-user-case-notes.ts similarity index 100% rename from web-api/src/persistence/postgres/utils/migrate/migrations/0004-add-user-case-notes.ts rename to web-api/src/persistence/postgres/utils/migrate/migrations/0003-add-user-case-notes.ts