From 423015f2efb7cd869fc70f67a8e1288285fd9782 Mon Sep 17 00:00:00 2001 From: Brianna Cerkiewicz Date: Wed, 28 Jun 2023 10:48:11 -0700 Subject: [PATCH 01/77] chore: create and use attachmentDeleteRouter --- app/server/index.ts | 3 ++ .../middleware/attachmentDeleteRouter.ts | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 app/server/middleware/attachmentDeleteRouter.ts diff --git a/app/server/index.ts b/app/server/index.ts index c6eeee1e46..7d3d1a929f 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -18,6 +18,7 @@ import ssoMiddleware from "./middleware/sso"; import graphqlUploadExpress from "graphql-upload/graphqlUploadExpress.js"; import config from "../config"; import attachmentDownloadRouter from "./middleware/attachmentDownloadRouter"; +import { attachmentDeleteRouter } from "/home/briannacerkiewicz/cas-cif/app/server/middleware/attachmentDeleteRouter"; const port = config.get("port"); const dev = config.get("env") !== "production"; @@ -61,6 +62,8 @@ app.prepare().then(async () => { server.use(graphqlUploadExpress()); server.use(attachmentDownloadRouter); + server.use(attachmentDeleteRouter); + server.get("*", async (req, res) => { return handle(req, res); }); diff --git a/app/server/middleware/attachmentDeleteRouter.ts b/app/server/middleware/attachmentDeleteRouter.ts new file mode 100644 index 0000000000..f316610f7f --- /dev/null +++ b/app/server/middleware/attachmentDeleteRouter.ts @@ -0,0 +1,42 @@ +import { Router } from "express"; +import { performQuery } from "./graphql"; +import { Storage } from "@google-cloud/storage"; +import config from "../../config"; + +export const attachmentDeleteRouter = Router(); + +const attachmentDetailsQuery = `query AttachmentDetailsQuery($attachmentId: ID!){ + attachment(id: $attachmentId) { + file + } +}`; + +export const handleDelete = async (req, res, next) => { + try { + const attachmentQueryVariables = { + attachmentId: req.params.attachmentId, + }; + + const result = await performQuery( + attachmentDetailsQuery, + attachmentQueryVariables, + req + ); + + const { + data: { + attachment: { file }, + }, + } = result; + + const storageClient = new Storage(); + const bucketName = config.get("attachmentsBucket"); + + const response = await storageClient.bucket(bucketName).file(file).delete(); + return res.sendStatus(response[0].statusCode); + } catch (error) { + next(error); + } +}; + +attachmentDeleteRouter.get("/delete/:attachmentId", handleDelete); From bb785d0d5a5baf950f79228905b18aef27016080 Mon Sep 17 00:00:00 2001 From: Brianna Cerkiewicz Date: Wed, 28 Jun 2023 10:52:03 -0700 Subject: [PATCH 02/77] test: jest test attachmentDeleteRouter --- .../attachmentDeleteRouter.test.tsx | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/tests/unit/server/middleware/attachmentDeleteRouter.test.tsx diff --git a/app/tests/unit/server/middleware/attachmentDeleteRouter.test.tsx b/app/tests/unit/server/middleware/attachmentDeleteRouter.test.tsx new file mode 100644 index 0000000000..47edf8378d --- /dev/null +++ b/app/tests/unit/server/middleware/attachmentDeleteRouter.test.tsx @@ -0,0 +1,36 @@ +/** + * @jest-environment node + */ + +import { + attachmentDeleteRouter, + handleDelete, +} from "server/middleware/attachmentDeleteRouter"; + +describe("The attachment delete router", () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it("is configured with the right route and handler", () => { + const routerUnderTest = attachmentDeleteRouter; + + expect(routerUnderTest.stack).toHaveLength(1); + expect(routerUnderTest.stack[0].route.path).toBe("/delete/:attachmentId"); + expect(attachmentDeleteRouter.stack[0].route.stack[0].handle).toBe( + handleDelete + ); + }); + + it("calls next() with the error if an exception is thrown", async () => { + const error = new TypeError( + "Cannot read properties of undefined (reading 'attachmentId')" + ); + const next = jest.fn(); + const handlerUnderTest = handleDelete; + + handlerUnderTest({ garbage: "i will cause an error" }, {}, next); + + expect(next).toHaveBeenCalledWith(error); + }); +}); From dec1096ce9d4b1a3e15a9cdaa653c8c3c24f2e49 Mon Sep 17 00:00:00 2001 From: Brianna Cerkiewicz Date: Wed, 28 Jun 2023 13:51:33 -0700 Subject: [PATCH 03/77] chore: create delete page route --- app/routes/pageRoutes.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/routes/pageRoutes.ts b/app/routes/pageRoutes.ts index ba1dfd6410..3387bc8497 100644 --- a/app/routes/pageRoutes.ts +++ b/app/routes/pageRoutes.ts @@ -149,6 +149,10 @@ export const getAttachmentDownloadRoute = (attachmentId: string) => ({ pathname: `/download/${attachmentId}`, }); +export const getAttachmentDeleteRoute = (attachmentId: string) => ({ + pathname: `/delete/${attachmentId}`, +}); + //// External User export const getExternalUserLandingPageRoute = () => ({ From f8070c3352cff78a9a61500e845745d2ec1d3066 Mon Sep 17 00:00:00 2001 From: Brianna Cerkiewicz Date: Wed, 28 Jun 2023 13:52:07 -0700 Subject: [PATCH 04/77] feat: hard delete attachments from GCS --- .../Attachment/AttachmentTableRow.tsx | 35 +++++--- .../Form/ProjectAttachmentsForm.tsx | 4 +- .../[projectRevision]/index.tsx | 85 ++++++++++++++----- .../middleware/attachmentDeleteRouter.ts | 2 +- 4 files changed, 93 insertions(+), 33 deletions(-) diff --git a/app/components/Attachment/AttachmentTableRow.tsx b/app/components/Attachment/AttachmentTableRow.tsx index 94ad3cc2c6..7a1e02d78d 100644 --- a/app/components/Attachment/AttachmentTableRow.tsx +++ b/app/components/Attachment/AttachmentTableRow.tsx @@ -1,6 +1,9 @@ import { Button } from "@button-inc/bcgov-theme"; import Link from "next/link"; -import { getAttachmentDownloadRoute } from "routes/pageRoutes"; +import { + getAttachmentDeleteRoute, + getAttachmentDownloadRoute, +} from "routes/pageRoutes"; import { graphql, useFragment } from "react-relay"; import useDiscardProjectAttachmentFormChange from "mutations/attachment/discardProjectAttachmentFormChange"; import { AttachmentTableRow_attachment$key } from "__generated__/AttachmentTableRow_attachment.graphql"; @@ -10,6 +13,7 @@ interface Props { connectionId: string; formChangeRowId: number; hideDelete?: boolean; + isFirstRevision: boolean; } const AttachmentTableRow: React.FC = ({ @@ -17,6 +21,7 @@ const AttachmentTableRow: React.FC = ({ connectionId, formChangeRowId, hideDelete, + isFirstRevision, }) => { const [ discardProjectAttachmentFormChange, @@ -45,15 +50,25 @@ const AttachmentTableRow: React.FC = ({ attachment ); - const handleArchiveAttachment = () => { - discardProjectAttachmentFormChange({ - variables: { - input: { - formChangeId: formChangeRowId, + const handleArchiveAttachment = (attachmentId) => { + const deleteFromApp = () => + discardProjectAttachmentFormChange({ + variables: { + input: { + formChangeId: formChangeRowId, + }, + connections: [connectionId], }, - connections: [connectionId], - }, - }); + }); + + if (isFirstRevision) { + // delete from cloud storage + fetch(getAttachmentDeleteRoute(attachmentId).pathname).then(() => { + return deleteFromApp(); + }); + } else { + deleteFromApp(); + } }; return ( @@ -70,7 +85,7 @@ const AttachmentTableRow: React.FC = ({ {!hideDelete && (