import axios from "axios"; import i18n from "i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; import { axiosAuthInterceptorId } from "../../utils/CleanAxios"; import client from "../../utils/GraphQLClient"; import exifr from "exifr"; import { store } from "../../redux/store"; //Context: currentUserEmail, bodyshop, jobid, invoiceid //Required to prevent headers from getting set and rejected from Cloudinary. var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); export const handleUpload = (ev, context, notification) => { logImEXEvent("document_upload", { filetype: ev.file.type }); const { onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; const fileName = ev.file.name || ev.filename; let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}`; let extension = fileName.split(".").pop(); uploadToCloudinary(key, extension, ev.file.type, ev.file, onError, onSuccess, onProgress, context, notification); }; export const uploadToCloudinary = async ( key, extension, fileType, file, onError, onSuccess, onProgress, context, notification ) => { const { bodyshop, jobId, billId, uploaded_by, callback, tagsArray } = context; //Set variables for getting the signed URL. let timestamp = Math.floor(Date.now() / 1000); let public_id = key; let tags = `${bodyshop.imexshopid},${tagsArray ? tagsArray.map((tag) => `${tag},`) : ""}`; // let eager = import.meta.env.VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS; //Get the signed url. const upload_preset = fileType.startsWith("video") ? "incoming_upload_video" : "incoming_upload"; const signedURLResponse = await axios.post("/media/sign", { public_id: public_id, tags: tags, timestamp: timestamp, upload_preset: upload_preset }); if (signedURLResponse.status !== 200) { if (onError) onError(signedURLResponse.statusText); notification["error"]({ message: i18n.t("documents.errors.getpresignurl", { message: signedURLResponse.statusText }) }); return; } //Build request to end to cloudinary. var signature = signedURLResponse.data; var options = { headers: { "X-Requested-With": "XMLHttpRequest" }, onUploadProgress: (e) => { if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); } }; const formData = new FormData(); formData.append("file", file); formData.append("upload_preset", upload_preset); formData.append("api_key", import.meta.env.VITE_APP_CLOUDINARY_API_KEY); formData.append("public_id", public_id); formData.append("tags", tags); formData.append("timestamp", timestamp); formData.append("signature", signature); const cloudinaryUploadResponse = await cleanAxios.post( `${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT_API}/${DetermineFileType(fileType)}/upload`, formData, { ...options } ); if (cloudinaryUploadResponse.status !== 200) { if (onError) { onError(cloudinaryUploadResponse.statusText); } try { axios.post("/newlog", { message: "client-cloudinary-upload-error", type: "error", user: store.getState().user.email, object: cloudinaryUploadResponse }); } catch { // NO OP } notification["error"]({ message: i18n.t("documents.errors.insert", { message: cloudinaryUploadResponse.statusText }) }); return; } //Insert the document with the matching key. let takenat; if (fileType.includes("image")) { try { const exif = await exifr.parse(file); takenat = exif && exif.DateTimeOriginal; } catch { console.log("Unable to parse image file for EXIF Data"); } } const documentInsert = await client.mutate({ mutation: INSERT_NEW_DOCUMENT, variables: { docInput: [ { ...(jobId ? { jobid: jobId } : {}), ...(billId ? { billid: billId } : {}), uploaded_by: uploaded_by, key: key, type: fileType, extension: cloudinaryUploadResponse.data.format || extension, bodyshopid: bodyshop.id, size: cloudinaryUploadResponse.data.bytes || file.size, takenat } ] } }); if (!documentInsert.errors) { if (onSuccess) onSuccess({ uid: documentInsert.data.insert_documents.returning[0].id, name: documentInsert.data.insert_documents.returning[0].name, status: "done", key: documentInsert.data.insert_documents.returning[0].key }); notification.open({ type: "success", key: "docuploadsuccess", message: i18n.t("documents.successes.insert") }); if (callback) { callback(); } } else { if (onError) onError(JSON.stringify(documentInsert.errors)); notification["error"]({ message: i18n.t("documents.errors.insert", { message: JSON.stringify(documentInsert.errors) }) }); return; } }; //Also needs to be updated in media JS and mobile app. export function DetermineFileType(filetype) { if (!filetype) return "auto"; else if (filetype.startsWith("image")) return "image"; else if (filetype.startsWith("video")) return "video"; else if (filetype.startsWith("application/pdf")) return "image"; else if (filetype.startsWith("application")) return "raw"; return "auto"; } function replaceAccents(str) { // Verifies if the String has accents and replace them if (str.search(/[\xC0-\xFF]/g) > -1) { str = str .replace(/[\xC0-\xC5]/g, "A") .replace(/[\xC6]/g, "AE") .replace(/[\xC7]/g, "C") .replace(/[\xC8-\xCB]/g, "E") .replace(/[\xCC-\xCF]/g, "I") .replace(/[\xD0]/g, "D") .replace(/[\xD1]/g, "N") .replace(/[\xD2-\xD6\xD8]/g, "O") .replace(/[\xD9-\xDC]/g, "U") .replace(/[\xDD]/g, "Y") .replace(/[\xDE]/g, "P") .replace(/[\xE0-\xE5]/g, "a") .replace(/[\xE6]/g, "ae") .replace(/[\xE7]/g, "c") .replace(/[\xE8-\xEB]/g, "e") .replace(/[\xEC-\xEF]/g, "i") .replace(/[\xF1]/g, "n") .replace(/[\xF2-\xF6\xF8]/g, "o") .replace(/[\xF9-\xFC]/g, "u") .replace(/[\xFE]/g, "p") .replace(/[\xFD\xFF]/g, "y"); } return str; }