Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
BEGIN TRY

BEGIN TRAN;

-- AlterTable
ALTER TABLE [dbo].[Folder] ADD [documentCount] INT,
[path] NVARCHAR(1000);

COMMIT TRAN;

END TRY
BEGIN CATCH

IF @@TRANCOUNT > 0
BEGIN
ROLLBACK TRAN;
END;
THROW

END CATCH
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
BEGIN TRY

BEGIN TRAN;

-- AlterTable
ALTER TABLE [dbo].[Folder] ADD CONSTRAINT [Folder_documentCount_df] DEFAULT 0 FOR [documentCount];

COMMIT TRAN;

END TRY
BEGIN CATCH

IF @@TRANCOUNT > 0
BEGIN
ROLLBACK TRAN;
END;
THROW

END CATCH
2 changes: 2 additions & 0 deletions apps/api/src/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ model Folder {
childFolders Folder[] @relation("FolderTree")
stage String?
ExaminationTimetableItem ExaminationTimetableItem[]
documentCount Int? @default(0)
path String?

@@unique([caseId, displayNameEn, parentFolderId, deletedAt])
}
Expand Down
63 changes: 63 additions & 0 deletions apps/api/src/database/seed/create-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { databaseConnector } from '#utils/database-connector.js';
import BackOfficeAppError from '#utils/app-error.js';

// starting from the root folders, create the path for every child folder, stop when leaf node reached

// get all root folders

// for each root folder traverse the tree/folder populating the path column, until leaf reached
/**
*
* @param folder
* @param {string | null} parentsPath
* @returns {Promise<void>}
*/
async function writePaths(folder, parentsPath) {
// Is it a root folder?
if (folder.parentFolderId == null) {
try {
const updatedRootFolder = await databaseConnector.folder.update({
where: { id: folder.id },
data: { path: `/${folder.id}` }
});
// create paths for child folders
const childFolders = await databaseConnector.folder.findMany({
where: { parentFolderId: folder.id }
});
if (!childFolders || childFolders.length === 0) return;

for (const childFolder of childFolders) {
await writePaths(childFolder, updatedRootFolder.path);
}
} catch (e) {
throw new BackOfficeAppError(`Error setting path for ${folder.id}`);
}
} else {
try {
// for child/non-root folders...
const updatedFolder = await databaseConnector.folder.update({
where: { id: folder.id },
data: { path: `${parentsPath}/${folder.id}` }
});

const childFolders = await databaseConnector.folder.findMany({
where: { parentFolderId: folder.id }
});

if (!childFolders || childFolders.length === 0) return;

for (const childFolder of childFolders) {
await writePaths(childFolder, updatedFolder.path);
}
} catch (e) {
throw new BackOfficeAppError(`Error setting path for ${folder.id}`);
}
}
}

(async () => {
const rootFolders = await databaseConnector.folder.findMany({
where: { parentFolderId: null }
});
await Promise.all(rootFolders.map((rootFolder) => writePaths(rootFolder, null)));
})();
32 changes: 32 additions & 0 deletions apps/api/src/database/test-scripts/create-paths-test-script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { databaseConnector } from '#utils/database-connector.js';
import assert from 'assert';

/*
* This script checks the paths for all folders
*
* usage: `cd` into directory, then `node create-paths-test-script.js`
* This test script was designed to run using the `node` command
* instead of `npm run test`, so it can run on a server that does not have
* libraries for unit testing installed, e.g. production
*
*/

const rootFolders = await databaseConnector.folder.findMany({
where: {
parentFolderId: null
}
});

await Promise.all(rootFolders.map((folder) => checkPaths(folder, folder.path)));

async function checkPaths(folder, parentPath) {
const expectedPath = folder.parentFolderId ? `${parentPath}/${folder.id}` : `/${folder.id}`;
assert.strictEqual(folder.path, expectedPath);
const childFolders = await databaseConnector.folder.findMany({
where: {
parentFolderId: folder.id
}
});
if (!childFolders || childFolders.length === 0) return;
for (const child of childFolders) await checkPaths(child, expectedPath);
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ export const startApplication = async (id) => {
data: {},
currentStatuses: caseDetails.CaseStatus,
setReference: true
},
folderRepository.createFolders(caseDetails.id)
}
);
await folderRepository.createFolders(caseDetails.id);

if (!updatedCase) {
throw new Error('Case does not exist');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,5 @@ export const verifyNotTraining = async (caseId) => {
) {
throw new Error(`Case with ID ${caseId} is a training case.`);
}
return projectWithSector;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EventType } from '@pins/event-client';
import { NSIP_DOCUMENT } from '#infrastructure/topics.js';
import { buildDocumentFolderPath } from '../document.service.js';
import { buildNsipDocumentPayload } from '#infrastructure/payload-builders/nsip-document.js';
import { jest } from '@jest/globals';
const { eventClient } = await import('#infrastructure/event-client.js');

const application = {
Expand Down Expand Up @@ -122,8 +123,22 @@ describe('Create documents', () => {
databaseConnector.case.findUnique.mockResolvedValue(application);

databaseConnector.folder.findMany.mockResolvedValue([{ id: 1, displayNameEn: 'folder 1' }]);
databaseConnector.folder.findUnique.mockResolvedValue({ id: 1, caseId: 1 });
databaseConnector.folder.findUnique.mockResolvedValue({
id: 1,
caseId: 1,
documentCount: 0,
path: '/1'
});
databaseConnector.document.create.mockResolvedValue({ id: 1, guid, name: 'test doc' });
databaseConnector.folder.findUnique.mockResolvedValue({
id: 1,
caseId: 1,
documentCount: 0,
path: '/1'
});
databaseConnector.$executeRaw = jest
.fn()
.mockResolvedValue({ id: 1, caseId: 1, documentCount: 1, path: '/1' });
databaseConnector.document.findFirst.mockResolvedValueOnce(null);
databaseConnector.documentVersion.upsert.mockResolvedValue(upsertedDocVersionResponse);
databaseConnector.document.update.mockResolvedValue(updatedDocResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ const folderContainingDocumentToDelete = {
case: {
id: 100000001,
reference: 'BC010001'
}
},
documentCount: 1,
path: '/10003'
};

const DocumentToDelete = {
Expand Down Expand Up @@ -163,6 +165,13 @@ describe('delete Document', () => {
documentVersionWithDocumentToDelete.Document.folder
);
databaseConnector.document.findUnique.mockResolvedValue(DocumentToDelete);
databaseConnector.folder.findUnique = jest
.fn()
.mockResolvedValue(folderContainingDocumentToDelete);
databaseConnector.$executeRaw = jest.fn().mockResolvedValue({
...folderContainingDocumentToDelete,
documentCount: 0
});
databaseConnector.case.findUnique.mockResolvedValue(application1);

const isDeleted = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import { getRedactionStatus, validateDocumentVersionMetadataBody } from './docum
*/
export const createDocumentsOnCase = async ({ params, body }, response) => {
const documentsToUpload = body[''];

const latestDocumentReference =
await documentRepository.getLatestDocReferenceByCaseIdExcludingMigrated({
caseId: /** @type {number} */ (params.id)
Expand Down Expand Up @@ -214,7 +213,7 @@ export const updateDocuments = async ({ body }, response) => {
* @type {import('express').RequestHandler<{id: number}, any, any, any>}
* */
export const moveDocumentsToAnotherFolder = async ({ body }, response) => {
const { documents, destinationFolderId, destinationFolderStage } = body;
const { documents, destinationFolderId, destinationFolderStage, currentFolderId } = body;

const documentNamesToMove = documents.map((document) => document.fileName);
const destinationFolderDocuments = await documentRepository.getDocumentsInFolder(
Expand Down Expand Up @@ -250,6 +249,8 @@ export const moveDocumentsToAnotherFolder = async ({ body }, response) => {
destinationFolderStage
});

// updateDocuments[0] is the array of Document objects updated
// updateDocuments[1] is array of DocumentVersion objects
if (updateDocuments[0].count === 0 || updateDocuments[1].count === 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could rename updateDocuments[0] to updatedDocumentObjects and updateDocuments[1] could be renamed to documentVersionObjects something like const [updatedDocumentObjects, documentVersionObjects] = updateDocuments;

return response.send({
errors: {
Expand All @@ -258,6 +259,12 @@ export const moveDocumentsToAnotherFolder = async ({ body }, response) => {
});
}

// decrease documentCount for current folder and ancestors
await folderRepository.decreaseDocumentCount(Number(currentFolderId), updateDocuments[0].count);

// update documentCount for new folder and ancestors
await folderRepository.increaseDocumentCount(destinationFolderId, updateDocuments[0].count);

response.send(updateDocuments);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import { mapDocumentVersionDetails } from '#utils/mapping/map-document-details.j
import * as documentRepository from '#repositories/document.repository.js';
import * as documentVersionRepository from '#repositories/document-metadata.repository.js';
import * as documentActivityLogRepository from '#repositories/document-activity-log.repository.js';
import { getFolderWithParents, getS51AdviceFolder } from '#repositories/folder.repository.js';
import {
getFolderWithParents,
getS51AdviceFolder,
decreaseDocumentCount,
increaseDocumentCount
} from '#repositories/folder.repository.js';
import { getStorageLocation } from '#utils/document-storage.js';
import BackOfficeAppError from '#utils/app-error.js';
import logger from '#utils/logger.js';
Expand Down Expand Up @@ -144,6 +149,7 @@ const attemptInsertDocuments = async (caseId, documents, isS51) => {
fromFrontOffice: documentToDB.fromFrontOffice,
documentType: isS51 ? DOCUMENT_TYPES.S51Attachment : DOCUMENT_TYPES.Document
});
await increaseDocumentCount(document.folderId);
} catch (err) {
logger.error(err);
failed.add(documentToDB.documentName);
Expand Down Expand Up @@ -1067,7 +1073,10 @@ export const deleteDocument = async (guid, caseId) => {
// step 3: mark the document as deleted
const deletedDocument = await documentRepository.deleteDocument(guid);

// Step 4: broadcast event message - ignoring training cases
// step 4: decrement documentCount of folder and ancestor folders
await decreaseDocumentCount(deletedDocument.folderId);

// Step 5: broadcast event message - ignoring training cases
await broadcastNsipDocumentEvent(documentToDelete, EventType.Delete);

return deletedDocument;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
checkFoldersHaveNoDocuments,
checkIfFolderIsCustom
} from './folders.service.js';
import { setPath } from '#repositories/folder.repository.js';

/**
* Handles a GET request for multiple folders and sends the corresponding details in the request
Expand Down Expand Up @@ -77,6 +78,7 @@ export const createFolder = async ({ params, body }, response) => {
}

const folder = await svcCreateFolder(params.id, body.name, body.parentFolderId);
await setPath(folder.id, folder.parentFolderId);
response.send(folder);
};

Expand Down
Loading
Loading