diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx index bbde76390..9383cf979 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx @@ -5,9 +5,7 @@ import axios from "axios"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; -import { DELETE_DOCUMENT } from "../../graphql/documents.queries"; -import cleanAxios from "../../utils/CleanAxios"; -import { DetermineFileType } from "../documents-upload/documents-upload.utility"; +import { DELETE_DOCUMENTS } from "../../graphql/documents.queries"; //Context: currentUserEmail, bodyshop, jobid, invoiceid export default function JobsDocumentsDeleteButton({ @@ -15,73 +13,62 @@ export default function JobsDocumentsDeleteButton({ deletionCallback, }) { const { t } = useTranslation(); - const [deleteDocument] = useMutation(DELETE_DOCUMENT); + const [deleteDocument] = useMutation(DELETE_DOCUMENTS); const imagesToDelete = [ ...galleryImages.images.filter((image) => image.isSelected), ...galleryImages.other.filter((image) => image.isSelected), ]; const [loading, setLoading] = useState(false); - const handleDelete = () => { + console.log( + "🚀 ~ file: jobs-documents-gallery.delete.component.jsx ~ line 29 ~ imagesToDelete", + imagesToDelete + ); + const handleDelete = async () => { logImEXEvent("job_documents_delete", { count: imagesToDelete.length }); setLoading(true); - imagesToDelete.forEach((image) => { - let timestamp = Math.floor(Date.now() / 1000); - let public_id = image.key; - - axios - .post("/media/sign", { - public_id: public_id, - timestamp: timestamp, - }) - .then((response) => { - var signature = response.data; - var options = { - headers: { "X-Requested-With": "XMLHttpRequest" }, - }; - const formData = new FormData(); - formData.append("api_key", process.env.REACT_APP_CLOUDINARY_API_KEY); - formData.append("public_id", public_id); - formData.append("timestamp", timestamp); - formData.append("signature", signature); - - cleanAxios - .post( - `${ - process.env.REACT_APP_CLOUDINARY_ENDPOINT_API - }/${DetermineFileType(image.type)}/destroy`, - formData, - options - ) - .then((response) => { - deleteDocument({ variables: { id: image.id } }) - .then((r) => { - notification.open({ - key: "docdeletedsuccesfully", - type: "success", - message: t("documents.successes.delete"), - }); - - if (deletionCallback) deletionCallback(); - }) - .catch((error) => { - notification["error"]({ - message: t("documents.errors.deleting", { - message: JSON.stringify(error), - }), - }); - }); - //Delete it from our database. - }) - .catch((error) => { - notification["error"]({ - message: t("documents.errors.deleting_cloudinary", { - message: JSON.stringify(error), - }), - }); - }); - }); + const res = await axios.post("/media/delete", { + ids: imagesToDelete, }); + console.log( + "🚀 ~ file: jobs-documents-gallery.delete.component.jsx ~ line 31 ~ res", + res + ); + const successfulDeletes = []; + Object.keys(res.data.deleted).forEach((key) => { + if (res.data.deleted[key] !== "deleted") { + notification["error"]({ + message: t("documents.errors.deleting_cloudinary", { + message: JSON.stringify(res.data.deleted[key]), + }), + }); + } else { + successfulDeletes.push(key); + } + }); + const delres = await deleteDocument({ + variables: { + ids: imagesToDelete + .filter((i) => successfulDeletes.includes(i.key)) + .map((i) => i.id), + }, + }); + if (delres.errors) { + notification["error"]({ + message: t("documents.errors.deleting", { + message: JSON.stringify(delres.errors), + }), + }); + } else { + notification.open({ + key: "docdeletedsuccesfully", + type: "success", + message: t("documents.successes.delete"), + }); + + if (deletionCallback) deletionCallback(); + } + setLoading(false); }; diff --git a/client/src/components/vehicles-list/vehicles-list.component.jsx b/client/src/components/vehicles-list/vehicles-list.component.jsx index 294dfb7ea..ca9091e67 100644 --- a/client/src/components/vehicles-list/vehicles-list.component.jsx +++ b/client/src/components/vehicles-list/vehicles-list.component.jsx @@ -30,7 +30,9 @@ export default function VehiclesListComponent({ dataIndex: "v_vin", key: "v_vin", render: (text, record) => ( - {record.v_vin} + + {record.v_vin || "N/A"} + ), }, { @@ -39,7 +41,9 @@ export default function VehiclesListComponent({ key: "description", render: (text, record) => { return ( - {`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc} ${record.v_color}`} + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + } ${record.v_color || ""}`} ); }, }, @@ -48,7 +52,9 @@ export default function VehiclesListComponent({ dataIndex: "plate", key: "plate", render: (text, record) => { - return {`${record.plate_st} | ${record.plate_no}`}; + return ( + {`${record.plate_st || ""} | ${record.plate_no || ""}`} + ); }, }, ]; diff --git a/client/src/graphql/documents.queries.js b/client/src/graphql/documents.queries.js index f9cc8877b..b2b754949 100644 --- a/client/src/graphql/documents.queries.js +++ b/client/src/graphql/documents.queries.js @@ -84,7 +84,15 @@ export const DELETE_DOCUMENT = gql` } } `; - +export const DELETE_DOCUMENTS = gql` + mutation DELETE_DOCUMENTS($ids: [uuid!]!) { + delete_documents(where: { id: { _in: $ids } }) { + returning { + id + } + } + } +`; export const QUERY_TEMPORARY_DOCS = gql` query QUERY_TEMPORARY_DOCS { documents( diff --git a/server.js b/server.js index 13f6b818e..8f37796a6 100644 --- a/server.js +++ b/server.js @@ -78,6 +78,7 @@ app.post( ); app.post("/media/download", fb.validateFirebaseIdToken, media.downloadFiles); app.post("/media/rename", fb.validateFirebaseIdToken, media.renameKeys); +app.post("/media/delete", fb.validateFirebaseIdToken, media.deleteFiles); //SMS/Twilio Paths var smsReceive = require("./server/sms/receive"); diff --git a/server/media/media.js b/server/media/media.js index e02beeac5..727c040c9 100644 --- a/server/media/media.js +++ b/server/media/media.js @@ -1,4 +1,5 @@ const path = require("path"); +const _ = require("lodash"); require("dotenv").config({ path: path.resolve( process.cwd(), @@ -22,6 +23,7 @@ exports.createSignedUploadURL = (req, res) => { exports.downloadFiles = (req, res) => { const { ids } = req.body; + const url = cloudinary.utils.download_zip_url({ public_ids: ids, flatten_folders: true, @@ -29,6 +31,45 @@ exports.downloadFiles = (req, res) => { res.send(url); }; +exports.deleteFiles = async (req, res) => { + const { ids } = req.body; + const types = _.groupBy(ids, (x) => DetermineFileType(x.type)); + console.log("🚀 ~ file: media.js ~ line 28 ~ types", types); + + const returns = []; + if (types.image) { + //delete images + + returns.push( + await cloudinary.api.delete_resources( + types.image.map((x) => x.key), + { resource_type: "image" } + ) + ); + } + if (types.video) { + //delete images returns.push( + returns.push( + await cloudinary.api.delete_resources( + types.video.map((x) => x.key), + { resource_type: "video" } + ) + ); + } + if (types.raw) { + //delete images returns.push( + returns.push( + await cloudinary.api.delete_resources( + types.raw.map((x) => x.key), + { resource_type: "raw" } + ) + ); + } + console.log("🚀 ~ file: media.js ~ line 40 ~ returns", returns); + + res.send(returns); +}; + exports.renameKeys = async (req, res) => { const { documents } = req.body; //{id: "", from: "", to:""}