feature/IO-2282-VSSTA-Integration: - checkpoint

This commit is contained in:
Dave Richer
2025-04-15 11:29:44 -04:00
parent d444821cf7
commit 35a7222f5e
2 changed files with 36 additions and 28 deletions

View File

@@ -2856,15 +2856,10 @@ query GET_BODYSHOP_BY_MERCHANTID($merchantID: String!) {
// Define the GraphQL query to get a job by RO number and shop ID // Define the GraphQL query to get a job by RO number and shop ID
exports.GET_JOB_BY_RO_NUMBER_AND_SHOP_ID = ` exports.GET_JOB_BY_RO_NUMBER_AND_SHOP_ID = `
query GET_JOB_BY_RO_NUMBER_AND_SHOP_ID($roNumber: String!, $shopId: String!) { query GET_JOB_BY_RO_NUMBER_AND_SHOP_ID($roNumber: String!, $shopId: uuid!) {
jobs(where: {ro_number: {_eq: $roNumber}, shopid: {_eq: $shopId}}) { jobs(where: {ro_number: {_eq: $roNumber}, shopid: {_eq: $shopId}}, limit: 1) {
id id
shopid shopid
bodyshopid
bodyshop {
id
email
}
} }
} }
`; `;

View File

@@ -2,15 +2,14 @@ const axios = require("axios");
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const { GET_JOB_BY_RO_NUMBER_AND_SHOP_ID, INSERT_NEW_DOCUMENT } = require("../../graphql-client/queries"); const { GET_JOB_BY_RO_NUMBER_AND_SHOP_ID, INSERT_NEW_DOCUMENT } = require("../../graphql-client/queries");
const determineFileType = require("../../media/util/determineFileType");
const { InstanceRegion } = require("../../utils/instanceMgr"); const { InstanceRegion } = require("../../utils/instanceMgr");
const client = require("../../graphql-client/graphql-client").client; const client = require("../../graphql-client/graphql-client").client;
// Assume these are configured environment variables or constants const S3_BUCKET = process.env.IMGPROXY_DESTINATION_BUCKET;
const S3_BUCKET = process.env.S3_BUCKET || "your-s3-bucket-name";
const vsstaIntegrationRoute = async (req, res) => { const vsstaIntegrationRoute = async (req, res) => {
const { logger } = req; const { logger } = req;
try { try {
const requiredParams = [ const requiredParams = [
"shop_id", "shop_id",
@@ -28,7 +27,10 @@ const vsstaIntegrationRoute = async (req, res) => {
const missingParams = requiredParams.filter((param) => !req.body[param]); const missingParams = requiredParams.filter((param) => !req.body[param]);
if (missingParams.length > 0) { if (missingParams.length > 0) {
logger.error(`Missing required parameters: ${missingParams.join(", ")}`); logger.log(`vssta-integration-missing-param`, "error", "api", "vssta", {
params: missingParams
});
return res.status(400).json({ return res.status(400).json({
error: "Missing required parameters", error: "Missing required parameters",
missingParams missingParams
@@ -45,29 +47,31 @@ const vsstaIntegrationRoute = async (req, res) => {
}); });
if (!jobResult.jobs || jobResult.jobs.length === 0) { if (!jobResult.jobs || jobResult.jobs.length === 0) {
logger.error(`No job found for RO number ${ro_nbr} and shop ID ${shop_id}`); logger.log(`vssta-integration-missing-ro`, "error", "api", "vssta");
return res.status(404).json({ error: "Job not found" }); return res.status(404).json({ error: "Job not found" });
} }
const job = jobResult.jobs[0]; const job = jobResult.jobs[0];
logger.info(`Found job with ID ${job.id} for RO number ${ro_nbr}`);
logger.logger.info(`Found job with ID ${job.id} for RO number ${ro_nbr}`);
// 2. Download the PDF from the provided link // 2. Download the PDF from the provided link
logger.info(`Downloading PDF from ${pdf_download_link}`); logger.logger.info(`Downloading PDF from ${pdf_download_link}`);
const pdfResponse = await axios.get(pdf_download_link, { const pdfResponse = await axios.get(pdf_download_link, {
responseType: "arraybuffer", responseType: "arraybuffer"
headers: { // headers: {
"auth:token": company_api_key // "auth:token": company_api_key
} // }
}); });
// 3. Generate key for S3 // 3. Generate key for S3
const timestamp = Date.now(); const timestamp = Date.now();
const fileName = `VSSTA_${scan_type}_Scan_${timestamp}.pdf`; const fileName = `VSSTA_${scan_type}_Scan_${timestamp}.pdf`;
const s3Key = `${job.bodyshopid}/${job.id}/${fileName.replace(/[^A-Z0-9]+/gi, "_")}-${timestamp}.pdf`; const s3Key = `${job.shopid}/${job.id}/${fileName.replace(/[^A-Z0-9]+/gi, "_")}-${timestamp}.pdf`;
// 4. Generate presigned URL for S3 upload // 4. Generate presigned URL for S3 upload
logger.info(`Generating presigned URL for S3 key ${s3Key}`); logger.logger.info(`Generating presigned URL for S3 key ${s3Key}`);
const s3Client = new S3Client({ region: InstanceRegion() }); const s3Client = new S3Client({ region: InstanceRegion() });
@@ -81,7 +85,8 @@ const vsstaIntegrationRoute = async (req, res) => {
const presignedUrl = await getSignedUrl(s3Client, putCommand, { expiresIn: 360 }); const presignedUrl = await getSignedUrl(s3Client, putCommand, { expiresIn: 360 });
// 5. Upload file to S3 // 5. Upload file to S3
logger.info(`Uploading PDF to S3 with key ${s3Key}`); logger.logger.info(`Uploading PDF to S3 with key ${s3Key}`);
await axios.put(presignedUrl, pdfResponse.data, { await axios.put(presignedUrl, pdfResponse.data, {
headers: { "Content-Type": "application/pdf" } headers: { "Content-Type": "application/pdf" }
}); });
@@ -92,12 +97,13 @@ const vsstaIntegrationRoute = async (req, res) => {
uploaded_by: "VSSTA Integration", // Matches uploaded_by (text) uploaded_by: "VSSTA Integration", // Matches uploaded_by (text)
name: fileName, // Matches name (text, nullable) name: fileName, // Matches name (text, nullable)
key: s3Key, // Matches key (text, default: '0'::text) key: s3Key, // Matches key (text, default: '0'::text)
type: determineFileType("application/pdf"), // Matches type (text, nullable), using determineFileType // type: determineFileType("application/pdf"), // Matches type (text, nullable), using determineFileType
// Ask Patrick why determineFileType just returns image...
type: "application/pdf", // Matches type (text, nullable),
extension: "pdf", // Matches extension (text, nullable) extension: "pdf", // Matches extension (text, nullable)
bodyshopid: job.bodyshopid, // Matches bodyshopid (uuid, nullable) bodyshopid: job.shopid, // Matches bodyshopid (uuid, nullable)
size: pdfResponse.data.length, // Matches size (integer, default: 0) size: pdfResponse.data.length, // Matches size (integer, default: 0)
takenat: scan_time, // Matches takenat (timestamp with time zone, nullable) takenat: scan_time // Matches takenat (timestamp with time zone, nullable)
description: `VSSTA ${scan_type} scan for ${year} ${make} ${model}, performed by ${technician} at ${scan_time}` // Not in schema, will be ignored by the database
}; };
const documentInsert = await client.request(INSERT_NEW_DOCUMENT, { const documentInsert = await client.request(INSERT_NEW_DOCUMENT, {
@@ -105,17 +111,24 @@ const vsstaIntegrationRoute = async (req, res) => {
}); });
if (documentInsert.insert_documents?.returning?.length > 0) { if (documentInsert.insert_documents?.returning?.length > 0) {
logger.info(`Document created with ID ${documentInsert.insert_documents.returning[0].id}`); logger.logger.info(`Document created with ID ${documentInsert.insert_documents.returning[0].id}`);
return res.status(200).json({ return res.status(200).json({
message: "VSSTA integration successful", message: "VSSTA integration successful",
documentId: documentInsert.insert_documents.returning[0].id documentId: documentInsert.insert_documents.returning[0].id
}); });
} else { } else {
logger.error("Failed to create document record"); logger.log(`vssta-integration-failed-to-create-document-record`, "error", "api", "vssta", {
params: missingParams
});
return res.status(500).json({ error: "Failed to create document record" }); return res.status(500).json({ error: "Failed to create document record" });
} }
} catch (error) { } catch (error) {
logger.error(`VSSTA integration error: ${error.message}`, error); logger.log(`vssta-integration-general`, "error", "api", "vssta", {
error: error?.message,
stack: error?.stack
});
return res.status(500).json({ error: error.message }); return res.status(500).json({ error: error.message });
} }
}; };