IO-2049 Document delete&move on server side

This commit is contained in:
Patrick Fic
2022-09-19 14:48:06 -07:00
parent 38efe03889
commit 2db2c8edbf
9 changed files with 146 additions and 76 deletions

View File

@@ -13447,6 +13447,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>deleting</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>deleting_cloudinary</name> <name>deleting_cloudinary</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,14 +1,11 @@
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import { Button, Form, notification, Popover, Space } from "antd"; import { Button, Form, notification, Popover, Space } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
GET_DOC_SIZE_BY_JOB,
UPDATE_DOCUMENT,
} from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
@@ -23,7 +20,11 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(JobsDocumentsGalleryReassign); )(JobsDocumentsGalleryReassign);
export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) { export function JobsDocumentsGalleryReassign({
bodyshop,
galleryImages,
callback,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -36,34 +37,33 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
const client = useApolloClient(); const client = useApolloClient();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [updateDocument] = useMutation(UPDATE_DOCUMENT);
const updateImage = async (i, jobid) => { // const updateImage = async (i, jobid) => {
//Move the cloudinary image // //Move the cloudinary image
//Update it in the database. // //Update it in the database.
const result = await updateDocument({ // const result = await updateDocument({
variables: { // variables: {
id: i.id, // id: i.id,
document: { // document: {
key: i.public_id, // key: i.public_id,
jobid: jobid, // jobid: jobid,
}, // },
}, // },
}); // });
if (!!result.errors) { // if (!!result.errors) {
notification["error"]({ // notification["error"]({
message: t("documents.errors.updating", { // message: t("documents.errors.updating", {
message: JSON.stringify(result.errors), // message: JSON.stringify(result.errors),
}), // }),
}); // });
} else { // } else {
notification["success"]({ // notification["success"]({
message: t("documents.successes.updated"), // message: t("documents.successes.updated"),
}); // });
} // }
}; // };
const handleFinish = async ({ jobid }) => { const handleFinish = async ({ jobid }) => {
setLoading(true); setLoading(true);
@@ -96,6 +96,7 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
} }
const res = await axios.post("/media/rename", { const res = await axios.post("/media/rename", {
tojobid: jobid,
documents: selectedImages.map((i) => { documents: selectedImages.map((i) => {
//Need to check if the current key folder is null, or another job. //Need to check if the current key folder is null, or another job.
const currentKeys = i.key.split("/"); const currentKeys = i.key.split("/");
@@ -110,24 +111,21 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
}; };
}), }),
}); });
//Add in confirmation & errors.
if (callback) callback();
res.data if (res.errors) {
.filter((d) => d.error) notification["error"]({
.forEach((d) => { message: t("documents.errors.updating", {
notification["error"]({ message: t("documents.errors.updating") }); message: JSON.stringify(res.errors),
console.error("Error updating job document", d); }),
}); });
}
const proms = []; if (!res.mutationResult?.errors) {
notification["success"]({
res.data message: t("documents.successes.updated"),
.filter((d) => !d.error)
.forEach((d) => {
proms.push(updateImage(d, jobid));
}); });
}
await Promise.all(proms);
setVisible(false); setVisible(false);
setLoading(false); setLoading(false);
}; };

View File

@@ -125,7 +125,10 @@ function JobsDocumentsComponent({
deletionCallback={billsCallback || refetch} deletionCallback={billsCallback || refetch}
/> />
{!billId && ( {!billId && (
<JobsDocumentsGalleryReassign galleryImages={galleryImages} /> <JobsDocumentsGalleryReassign
galleryImages={galleryImages}
callback={refetch}
/>
)} )}
</Space> </Space>
</Col> </Col>

View File

@@ -1,11 +1,9 @@
import { QuestionCircleOutlined } from "@ant-design/icons"; import { QuestionCircleOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, notification, Popconfirm } from "antd"; import { Button, notification, Popconfirm } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_DOCUMENTS } from "../../graphql/documents.queries";
//Context: currentUserEmail, bodyshop, jobid, invoiceid //Context: currentUserEmail, bodyshop, jobid, invoiceid
export default function JobsDocumentsDeleteButton({ export default function JobsDocumentsDeleteButton({
@@ -13,7 +11,7 @@ export default function JobsDocumentsDeleteButton({
deletionCallback, deletionCallback,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [deleteDocument] = useMutation(DELETE_DOCUMENTS);
const imagesToDelete = [ const imagesToDelete = [
...galleryImages.images.filter((image) => image.isSelected), ...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected), ...galleryImages.other.filter((image) => image.isSelected),
@@ -27,31 +25,10 @@ export default function JobsDocumentsDeleteButton({
ids: imagesToDelete, ids: imagesToDelete,
}); });
const successfulDeletes = []; if (res.data.error) {
res.data.forEach((resType) => {
Object.keys(resType.deleted).forEach((key) => {
if (resType.deleted[key] !== "deleted") {
notification["error"]({
message: t("documents.errors.deleting_cloudinary", {
message: JSON.stringify(resType.deleted[key]),
}),
});
} else {
successfulDeletes.push(key.replace(/\.[^/.]+$/, ""));
}
});
});
const delres = await deleteDocument({
variables: {
ids: imagesToDelete
.filter((i) => successfulDeletes.includes(i.key))
.map((i) => i.id),
},
});
if (delres.errors) {
notification["error"]({ notification["error"]({
message: t("documents.errors.deleting", { message: t("documents.errors.deleting", {
message: JSON.stringify(delres.errors), error: JSON.stringify(res.data.error.response.errors),
}), }),
}); });
} else { } else {

View File

@@ -835,6 +835,7 @@
}, },
"errors": { "errors": {
"deletes3": "Error deleting document from storage. ", "deletes3": "Error deleting document from storage. ",
"deleting": "Error deleting documents {{error}}",
"deleting_cloudinary": "Error deleting document from storage. {{message}}", "deleting_cloudinary": "Error deleting document from storage. {{message}}",
"getpresignurl": "Error obtaining presigned URL for document. {{message}}", "getpresignurl": "Error obtaining presigned URL for document. {{message}}",
"insert": "Unable to upload file. {{message}}", "insert": "Unable to upload file. {{message}}",

View File

@@ -835,6 +835,7 @@
}, },
"errors": { "errors": {
"deletes3": "Error al eliminar el documento del almacenamiento.", "deletes3": "Error al eliminar el documento del almacenamiento.",
"deleting": "",
"deleting_cloudinary": "", "deleting_cloudinary": "",
"getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}", "getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}",
"insert": "Incapaz de cargar el archivo. {{message}}", "insert": "Incapaz de cargar el archivo. {{message}}",

View File

@@ -835,6 +835,7 @@
}, },
"errors": { "errors": {
"deletes3": "Erreur lors de la suppression du document du stockage.", "deletes3": "Erreur lors de la suppression du document du stockage.",
"deleting": "",
"deleting_cloudinary": "", "deleting_cloudinary": "",
"getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}", "getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}",
"insert": "Incapable de télécharger le fichier. {{message}}", "insert": "Incapable de télécharger le fichier. {{message}}",

View File

@@ -1611,3 +1611,13 @@ exports.INSERT_EMAIL_AUDIT = `mutation INSERT_EMAIL_AUDIT($email: email_audit_tr
} }
} }
`; `;
exports.DELETE_MEDIA_DOCUMENTS = `
mutation DELETE_DOCUMENTS($ids: [uuid!]!) {
delete_documents(where: { id: { _in: $ids } }) {
returning {
id
}
}
}
`;

View File

@@ -1,6 +1,8 @@
const path = require("path"); const path = require("path");
const _ = require("lodash"); const _ = require("lodash");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(
@@ -69,11 +71,38 @@ exports.deleteFiles = async (req, res) => {
); );
} }
res.send(returns); // Delete it on apollo.
const successfulDeletes = [];
returns.forEach((resType) => {
Object.keys(resType.deleted).forEach((key) => {
if (
resType.deleted[key] === "deleted" ||
resType.deleted[key] === "not_found"
) {
successfulDeletes.push(key.replace(/\.[^/.]+$/, ""));
}
});
});
try {
const result = await client.request(queries.DELETE_MEDIA_DOCUMENTS, {
ids: ids
.filter((i) => successfulDeletes.includes(i.key))
.map((i) => i.id),
});
res.send({ returns, result });
} catch (error) {
logger.log("media-delete-error", "ERROR", req.user.email, null, [
{ ids, error: error.message || JSON.stringify(error) },
]);
res.json({ error });
}
}; };
exports.renameKeys = async (req, res) => { exports.renameKeys = async (req, res) => {
const { documents } = req.body; const { documents, tojobid } = req.body;
logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents); logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents);
const proms = []; const proms = [];
@@ -98,8 +127,37 @@ exports.renameKeys = async (req, res) => {
let result; let result;
result = await Promise.all(proms); result = await Promise.all(proms);
const errors = [];
result
.filter((d) => d.error)
.forEach((d) => {
errors.push(d);
});
res.send(result); let mutations = "";
result
.filter((d) => !d.error)
.forEach((d, idx) => {
//Create mutation text
mutations =
mutations +
`
update_doc${idx}:update_documents_by_pk(pk_columns: { id: "${d.id}" }, _set: {key: "${d.public_id}", jobid: "${tojobid}"}){
id
}
`;
});
if (mutations !== "") {
const mutationResult = await client.request(`mutation {
${mutations}
}`);
res.json({ errors, mutationResult });
} else {
res.json({ errors: "No images were succesfully moved on remote server. " });
}
}; };
//Also needs to be updated in upload utility and mobile app. //Also needs to be updated in upload utility and mobile app.