From ad84a2ff95b9da6b9c7aabb89d21e14713bad8e6 Mon Sep 17 00:00:00 2001 From: Ethan Turner Date: Wed, 28 Aug 2024 18:45:10 -0700 Subject: [PATCH] feat(BooksManager): introduce option to Unpublish book but not delete project --- ...dal.tsx => UnpublishOrDeleteBookModal.tsx} | 27 ++++++++++----- .../controlpanel/BooksManager/index.jsx | 34 ++++++++++++------- server/api.js | 2 +- server/api/books.ts | 15 +++++--- server/api/validators/book.ts | 9 +++++ 5 files changed, 60 insertions(+), 27 deletions(-) rename client/src/components/controlpanel/BooksManager/{DeleteBookModal.tsx => UnpublishOrDeleteBookModal.tsx} (70%) diff --git a/client/src/components/controlpanel/BooksManager/DeleteBookModal.tsx b/client/src/components/controlpanel/BooksManager/UnpublishOrDeleteBookModal.tsx similarity index 70% rename from client/src/components/controlpanel/BooksManager/DeleteBookModal.tsx rename to client/src/components/controlpanel/BooksManager/UnpublishOrDeleteBookModal.tsx index 3578e96c..c3d752b5 100644 --- a/client/src/components/controlpanel/BooksManager/DeleteBookModal.tsx +++ b/client/src/components/controlpanel/BooksManager/UnpublishOrDeleteBookModal.tsx @@ -4,16 +4,18 @@ import { Button, Checkbox, Icon, Modal, ModalProps } from "semantic-ui-react"; import useGlobalError from "../../error/ErrorHooks"; import { useNotifications } from "../../../context/NotificationContext"; -interface DeleteBookModalProps extends ModalProps { +interface UnpublishOrDeleteBookModalProps extends ModalProps { bookID: string; bookTitle: string; + deleteMode: boolean; onClose: () => void; open: boolean; } -export const DeleteBookModal: React.FC = ({ +export const UnpublishOrDeleteBookModal: React.FC = ({ bookID, bookTitle, + deleteMode, onClose, open, }) => { @@ -35,14 +37,18 @@ export const DeleteBookModal: React.FC = ({ if (!confirmCoverPage) return; try { setIsLoading(true); - const delRes = await axios.delete(`/commons/book/${bookID}`); + const delRes = await axios.delete(`/commons/book/${bookID}`, { + params: { + ...(deleteMode && { deleteProject: deleteMode }), + }, + }); setIsLoading(false); if (delRes.data.error) { handleGlobalError(delRes.data.errMsg); return; } addNotification({ - message: `"${bookTitle}" was successfully deleted.`, + message: deleteMode ? `"${bookTitle}" and associated Conductor project was successfully deleted.` : `"${bookTitle}" was successfully unpublished.`, type: 'success', }); handleClose(); @@ -58,9 +64,9 @@ export const DeleteBookModal: React.FC = ({ open={open} size="large" > - Delete Book + {deleteMode ? 'Delete Book and Project' : 'Unpublish Book'} -

Are you sure you want to delete the record for "{bookTitle}"? By clicking Delete, you confirm you understand the following:

+

Are you sure you want to {deleteMode ? 'delete the record for' : 'unpublish'} "{bookTitle}"? By clicking {deleteMode ? 'Delete' : 'Unpublish'}, you confirm you understand the following:

  • The entry will be removed from Commons. @@ -69,8 +75,11 @@ export const DeleteBookModal: React.FC = ({
  • Any submitted Adoption Reports for this Commons entry will be deleted.
+
  • The entry will be removed from any collections it is a part of.
  • The entry will be removed from the central downloads listings and vendor export lists.
  • -
  • The connected project (if applicable) and any related resources will be deleted.
  • + {deleteMode && ( +
  • The corresponding Conductor project (if applicable) and any related resources will be deleted.
  • + )}

    In order to continue, confirm the following:

    = ({ Cancel diff --git a/client/src/screens/conductor/controlpanel/BooksManager/index.jsx b/client/src/screens/conductor/controlpanel/BooksManager/index.jsx index 6f21ab99..5d11d38c 100644 --- a/client/src/screens/conductor/controlpanel/BooksManager/index.jsx +++ b/client/src/screens/conductor/controlpanel/BooksManager/index.jsx @@ -16,7 +16,7 @@ import { Segment, Table, } from 'semantic-ui-react'; -import { DeleteBookModal } from "../../../../components/controlpanel/BooksManager/DeleteBookModal"; +import { UnpublishOrDeleteBookModal } from "../../../../components/controlpanel/BooksManager/UnpublishOrDeleteBookModal"; import useGlobalError from '../../../../components/error/ErrorHooks'; import { useModals } from "../../../../context/ModalContext"; import { itemsPerPageOptions } from '../../../../components/util/PaginationOptions'; @@ -225,19 +225,20 @@ const BooksManager = () => { setEOCWorking(false); } - function closeDeleteModal() { + function closeUnpublishOrDeleteModal() { closeAllModals(); getBooks(); } - function openDeleteModal(bookID, bookTitle) { + function openUnpublishOrDeleteModal(bookID, bookTitle, deleteMode) { if (!bookID) return; openModal( - ); @@ -369,13 +370,22 @@ const BooksManager = () => { )} {isSuperAdmin && ( - + <> + + + )} diff --git a/server/api.js b/server/api.js index 0b288f59..728bf133 100644 --- a/server/api.js +++ b/server/api.js @@ -636,7 +636,7 @@ router.route('/commons/book/:bookID') booksAPI.getBookDetail, ) .delete( - middleware.validateZod(BookValidators.getWithBookIDParamSchema), + middleware.validateZod(BookValidators.deleteBookSchema), authAPI.verifyRequest, authAPI.getUserAttributes, authAPI.checkHasRoleMiddleware('libretexts', 'superadmin'), diff --git a/server/api/books.ts b/server/api/books.ts index 14655a3d..d8e7d5b0 100644 --- a/server/api/books.ts +++ b/server/api/books.ts @@ -68,6 +68,7 @@ const defaultImagesURL = "https://cdn.libretexts.net/DefaultImages"; import { PipelineStage } from "mongoose"; import { createBookSchema, + deleteBookSchema, getCommonsCatalogSchema, getMasterCatalogSchema, getWithBookIDParamSchema, @@ -1445,10 +1446,11 @@ async function createBook( * @param {express.Response} res - Outgoing response. */ async function deleteBook( - req: ZodReqWithUser>, + req: ZodReqWithUser>, res: Response, ) { try { + const deleteProject = !!req.query?.deleteProject; const bookID = req.params.bookID; const [lib, coverID] = getLibraryAndPageFromBookID(req.params.bookID); if (!lib || !coverID) { @@ -1467,11 +1469,14 @@ async function deleteBook( }); if (attachedProject) { const projectID = attachedProject.projectID; - const projDelRes = await projectsAPI.deleteProjectInternal(projectID); - if (!projDelRes) { - return conductor500Err(res); - } await PeerReview.deleteMany({ projectID }); + if (deleteProject) { + debug(`[Delete Book]: Deleting project ${projectID}`); + const projDelRes = await projectsAPI.deleteProjectInternal(projectID); + if (!projDelRes) { + return conductor500Err(res); + } + } } // diff --git a/server/api/validators/book.ts b/server/api/validators/book.ts index a818ce35..daf667b9 100644 --- a/server/api/validators/book.ts +++ b/server/api/validators/book.ts @@ -64,3 +64,12 @@ export const downloadBookFileSchema = z.object({ fileID: z.string().uuid(), }), }); + +export const deleteBookSchema = z.intersection( + z.object({ + query: z.object({ + deleteProject: z.coerce.boolean().optional(), + }), + }), + getWithBookIDParamSchema, +);