Compare commits
5 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89fabf85e1 | ||
|
|
2439755f9e | ||
|
|
763384f05f | ||
|
|
cba2da8da7 | ||
|
|
f3d8aa3438 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text eol=lf
|
||||||
30
.vscode/settings.json
vendored
30
.vscode/settings.json
vendored
@@ -8,35 +8,5 @@
|
|||||||
"pattern": "**/IMEX.xml",
|
"pattern": "**/IMEX.xml",
|
||||||
"systemId": "logs/IMEX.xsd"
|
"systemId": "logs/IMEX.xsd"
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"cSpell.words": [
|
|
||||||
"antd",
|
|
||||||
"appointmentconfirmation",
|
|
||||||
"appt",
|
|
||||||
"autohouse",
|
|
||||||
"autohouseid",
|
|
||||||
"billlines",
|
|
||||||
"bodyshop",
|
|
||||||
"bodyshopid",
|
|
||||||
"bodyshops",
|
|
||||||
"CIECA",
|
|
||||||
"claimscorp",
|
|
||||||
"claimscorpid",
|
|
||||||
"Dinero",
|
|
||||||
"driveable",
|
|
||||||
"IMEX",
|
|
||||||
"imexshopid",
|
|
||||||
"jobid",
|
|
||||||
"joblines",
|
|
||||||
"Kaizen",
|
|
||||||
"labhrs",
|
|
||||||
"larhrs",
|
|
||||||
"mixdata",
|
|
||||||
"ownr",
|
|
||||||
"promanager",
|
|
||||||
"shopname",
|
|
||||||
"smartscheduling",
|
|
||||||
"timetickets",
|
|
||||||
"touchtime"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
- SERVICES=ses,secretsmanager,cloudwatch,logs
|
- SERVICES=s3,ses,secretsmanager,cloudwatch,logs
|
||||||
- DEBUG=0
|
- DEBUG=0
|
||||||
- AWS_ACCESS_KEY_ID=test
|
- AWS_ACCESS_KEY_ID=test
|
||||||
- AWS_SECRET_ACCESS_KEY=test
|
- AWS_SECRET_ACCESS_KEY=test
|
||||||
@@ -115,7 +115,8 @@ services:
|
|||||||
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/id_rsa
|
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/id_rsa
|
||||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
"
|
"
|
||||||
# Node App: The Main IMEX API
|
# Node App: The Main IMEX API
|
||||||
node-app:
|
node-app:
|
||||||
|
|||||||
1297
package-lock.json
generated
1297
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-cloudwatch-logs": "^3.679.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.679.0",
|
||||||
"@aws-sdk/client-elasticache": "^3.675.0",
|
"@aws-sdk/client-elasticache": "^3.675.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.689.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.675.0",
|
"@aws-sdk/client-secrets-manager": "^3.675.0",
|
||||||
"@aws-sdk/client-ses": "^3.675.0",
|
"@aws-sdk/client-ses": "^3.675.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.675.0",
|
"@aws-sdk/credential-provider-node": "^3.675.0",
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
"soap": "^1.1.5",
|
"soap": "^1.1.5",
|
||||||
"socket.io": "^4.8.0",
|
"socket.io": "^4.8.0",
|
||||||
"socket.io-adapter": "^2.5.5",
|
"socket.io-adapter": "^2.5.5",
|
||||||
"ssh2-sftp-client": "^11.0.0",
|
"ssh2-sftp-client": "^10.0.3",
|
||||||
"twilio": "^4.23.0",
|
"twilio": "^4.23.0",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"winston": "^3.15.0",
|
"winston": "^3.15.0",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const { applyRedisHelpers } = require("./server/utils/redisHelpers");
|
|||||||
const { applyIOHelpers } = require("./server/utils/ioHelpers");
|
const { applyIOHelpers } = require("./server/utils/ioHelpers");
|
||||||
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
|
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
|
||||||
const { ElastiCacheClient, DescribeCacheClustersCommand } = require("@aws-sdk/client-elasticache");
|
const { ElastiCacheClient, DescribeCacheClustersCommand } = require("@aws-sdk/client-elasticache");
|
||||||
const { default: InstanceManager } = require("./server/utils/instanceMgr");
|
const { InstanceRegion } = require("./server/utils/instanceMgr");
|
||||||
|
|
||||||
const CLUSTER_RETRY_BASE_DELAY = 100;
|
const CLUSTER_RETRY_BASE_DELAY = 100;
|
||||||
const CLUSTER_RETRY_MAX_DELAY = 5000;
|
const CLUSTER_RETRY_MAX_DELAY = 5000;
|
||||||
@@ -114,10 +114,7 @@ const applyRoutes = ({ app }) => {
|
|||||||
*/
|
*/
|
||||||
const getRedisNodesFromAWS = async () => {
|
const getRedisNodesFromAWS = async () => {
|
||||||
const client = new ElastiCacheClient({
|
const client = new ElastiCacheClient({
|
||||||
region: InstanceManager({
|
region: InstanceRegion()
|
||||||
imex: "ca-central-1",
|
|
||||||
rome: "us-east-2"
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ const ftpSetup = {
|
|||||||
port: process.env.AUTOHOUSE_PORT,
|
port: process.env.AUTOHOUSE_PORT,
|
||||||
username: process.env.AUTOHOUSE_USER,
|
username: process.env.AUTOHOUSE_USER,
|
||||||
password: process.env.AUTOHOUSE_PASSWORD,
|
password: process.env.AUTOHOUSE_PASSWORD,
|
||||||
debug:
|
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
|
||||||
process.env.NODE_ENV !== "production"
|
|
||||||
? (message, ...data) => logger.log(message, "DEBUG", "api", null, data)
|
|
||||||
: () => {},
|
|
||||||
algorithms: {
|
algorithms: {
|
||||||
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const allxmlsToUpload = [];
|
||||||
|
const allErrors = [];
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
// Only process if in production environment.
|
// Only process if in production environment.
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
@@ -47,21 +47,16 @@ exports.default = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send immediate response and continue processing.
|
// Send immediate response and continue processing.
|
||||||
res.status(202).json({
|
res.status(200).send();
|
||||||
success: true,
|
|
||||||
message: "Processing request ...",
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||||
const allXMLResults = [];
|
|
||||||
const allErrors = [];
|
|
||||||
|
|
||||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients.
|
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients.
|
||||||
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
||||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||||
|
|
||||||
|
const batchSize = 10;
|
||||||
|
|
||||||
const shopsToProcess =
|
const shopsToProcess =
|
||||||
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
||||||
logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null);
|
logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null);
|
||||||
@@ -70,18 +65,27 @@ exports.default = async (req, res) => {
|
|||||||
logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null);
|
logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const batchPromises = [];
|
||||||
|
for (let i = 0; i < shopsToProcess.length; i += batchSize) {
|
||||||
|
const batch = shopsToProcess.slice(i, i + batchSize);
|
||||||
|
const batchPromise = (async () => {
|
||||||
|
await processBatch(batch, start, end);
|
||||||
|
|
||||||
await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
|
if (skipUpload) {
|
||||||
|
for (const xmlObj of allxmlsToUpload) {
|
||||||
|
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await uploadViaSFTP(allxmlsToUpload);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
batchPromises.push(batchPromise);
|
||||||
|
}
|
||||||
|
await Promise.all(batchPromises);
|
||||||
await sendServerEmail({
|
await sendServerEmail({
|
||||||
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
|
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
|
||||||
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
||||||
allXMLResults.map((x) => ({
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })),
|
||||||
imexshopid: x.imexshopid,
|
|
||||||
filename: x.filename,
|
|
||||||
count: x.count,
|
|
||||||
result: x.result
|
|
||||||
})),
|
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
)}`
|
)}`
|
||||||
@@ -93,8 +97,8 @@ exports.default = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) {
|
async function processBatch(batch, start, end) {
|
||||||
for (const bodyshop of shopsToProcess) {
|
for (const bodyshop of batch) {
|
||||||
const erroredJobs = [];
|
const erroredJobs = [];
|
||||||
try {
|
try {
|
||||||
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
@@ -124,27 +128,12 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const xmlObj = {
|
const ret = builder.create({}, autoHouseObject).end({ allowEmptyTags: true });
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
xml: builder.create({}, autoHouseObject).end({ allowEmptyTags: true }),
|
|
||||||
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`,
|
|
||||||
count: autoHouseObject.AutoHouseExport.RepairOrder.length
|
|
||||||
};
|
|
||||||
|
|
||||||
if (skipUpload) {
|
allxmlsToUpload.push({
|
||||||
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
count: autoHouseObject.AutoHouseExport.RepairOrder.length,
|
||||||
} else {
|
xml: ret,
|
||||||
await uploadViaSFTP(xmlObj);
|
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`
|
||||||
}
|
|
||||||
|
|
||||||
allXMLResults.push({
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
autohouseid: bodyshop.autohouseid,
|
|
||||||
count: xmlObj.count,
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
result: xmlObj.result
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
@@ -157,7 +146,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes
|
|||||||
allErrors.push({
|
allErrors.push({
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
imexshopid: bodyshop.imexshopid,
|
imexshopid: bodyshop.imexshopid,
|
||||||
autohouseid: bodyshop.autohouseid,
|
autuhouseid: bodyshop.autuhouseid,
|
||||||
fatal: true,
|
fatal: true,
|
||||||
errors: [error.toString()]
|
errors: [error.toString()]
|
||||||
});
|
});
|
||||||
@@ -165,7 +154,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes
|
|||||||
allErrors.push({
|
allErrors.push({
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
imexshopid: bodyshop.imexshopid,
|
imexshopid: bodyshop.imexshopid,
|
||||||
autohouseid: bodyshop.autohouseid,
|
autuhouseid: bodyshop.autuhouseid,
|
||||||
errors: erroredJobs.map((ej) => ({
|
errors: erroredJobs.map((ej) => ({
|
||||||
ro_number: ej.job?.ro_number,
|
ro_number: ej.job?.ro_number,
|
||||||
jobid: ej.job?.id,
|
jobid: ej.job?.id,
|
||||||
@@ -176,35 +165,34 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadViaSFTP(xmlObj) {
|
async function uploadViaSFTP(allxmlsToUpload) {
|
||||||
const sftp = new Client();
|
const sftp = new Client();
|
||||||
sftp.on("error", (errors) =>
|
sftp.on("error", (errors) =>
|
||||||
logger.log("autohouse-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, {
|
logger.log("autohouse-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
|
||||||
error: errors.message,
|
|
||||||
stack: errors.stack
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
//Connect to the FTP and upload all.
|
//Connect to the FTP and upload all.
|
||||||
await sftp.connect(ftpSetup);
|
await sftp.connect(ftpSetup);
|
||||||
|
|
||||||
try {
|
for (const xmlObj of allxmlsToUpload) {
|
||||||
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
|
try {
|
||||||
logger.log("autohouse-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
|
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { filename: xmlObj.filename });
|
||||||
imexshopid: xmlObj.imexshopid,
|
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
|
||||||
filename: xmlObj.filename,
|
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
|
||||||
result: xmlObj.result
|
filename: xmlObj.filename,
|
||||||
});
|
result: xmlObj.result
|
||||||
} catch (error) {
|
});
|
||||||
logger.log("autohouse-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
|
} catch (error) {
|
||||||
filename: xmlObj.filename,
|
logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, {
|
||||||
error: error.message,
|
filename: xmlObj.filename,
|
||||||
stack: error.stack
|
error: error.message,
|
||||||
});
|
stack: error.stack
|
||||||
throw error;
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("autohouse-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack });
|
logger.log("autohouse-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
sftp.end();
|
sftp.end();
|
||||||
@@ -621,7 +609,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
|||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, {
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
|
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ const ftpSetup = {
|
|||||||
port: process.env.CHATTER_PORT,
|
port: process.env.CHATTER_PORT,
|
||||||
username: process.env.CHATTER_USER,
|
username: process.env.CHATTER_USER,
|
||||||
privateKey: null,
|
privateKey: null,
|
||||||
debug: process.env.NODE_ENV !== "production"
|
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
|
||||||
? (message, ...data) => logger.log(message, "DEBUG", "api", null, data)
|
|
||||||
: () => {},
|
|
||||||
algorithms: {
|
algorithms: {
|
||||||
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
||||||
}
|
}
|
||||||
@@ -41,11 +39,7 @@ exports.default = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send immediate response and continue processing.
|
// Send immediate response and continue processing.
|
||||||
res.status(202).json({
|
res.status(200).send();
|
||||||
success: true,
|
|
||||||
message: "Processing request ...",
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log("chatter-start", "DEBUG", "api", null, null);
|
logger.log("chatter-start", "DEBUG", "api", null, null);
|
||||||
@@ -182,8 +176,9 @@ async function uploadViaSFTP(allcsvsToUpload) {
|
|||||||
|
|
||||||
for (const csvObj of allcsvsToUpload) {
|
for (const csvObj of allcsvsToUpload) {
|
||||||
try {
|
try {
|
||||||
|
logger.log("chatter-sftp-upload", "DEBUG", "api", null, { filename: csvObj.filename });
|
||||||
csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`);
|
csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`);
|
||||||
logger.log("chatter-sftp-upload", "DEBUG", "api", null, {
|
logger.log("chatter-sftp-upload-result", "DEBUG", "api", null, {
|
||||||
filename: csvObj.filename,
|
filename: csvObj.filename,
|
||||||
result: csvObj.result
|
result: csvObj.result
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,12 +24,9 @@ const ftpSetup = {
|
|||||||
port: process.env.CLAIMSCORP_PORT,
|
port: process.env.CLAIMSCORP_PORT,
|
||||||
username: process.env.CLAIMSCORP_USER,
|
username: process.env.CLAIMSCORP_USER,
|
||||||
password: process.env.CLAIMSCORP_PASSWORD,
|
password: process.env.CLAIMSCORP_PASSWORD,
|
||||||
debug:
|
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
|
||||||
process.env.NODE_ENV !== "production"
|
|
||||||
? (message, ...data) => logger.log(message, "DEBUG", "api", null, data)
|
|
||||||
: () => {},
|
|
||||||
algorithms: {
|
algorithms: {
|
||||||
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
serverHostKey: ["ssh-rsa", "ssh-dss"]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,183 +36,164 @@ exports.default = async (req, res) => {
|
|||||||
res.sendStatus(403);
|
res.sendStatus(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only process if the appropriate token is provided.
|
|
||||||
|
//Query for the List of Bodyshop Clients.
|
||||||
|
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
||||||
|
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
||||||
|
|
||||||
|
const specificShopIds = req.body.bodyshopIds; // ['uuid]
|
||||||
|
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||||
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
||||||
res.sendStatus(401);
|
res.sendStatus(401);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const allxmlsToUpload = [];
|
||||||
// Send immediate response and continue processing.
|
const allErrors = [];
|
||||||
res.status(202).json({
|
|
||||||
success: true,
|
|
||||||
message: "Processing request ...",
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) {
|
||||||
const allXMLResults = [];
|
|
||||||
const allErrors = [];
|
|
||||||
|
|
||||||
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); //Query for the List of Bodyshop Clients.
|
|
||||||
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
|
||||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
|
||||||
|
|
||||||
const shopsToProcess =
|
|
||||||
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
|
||||||
logger.log("claimscorp-shopsToProcess-generated", "DEBUG", "api", null, null);
|
|
||||||
|
|
||||||
if (shopsToProcess.length === 0) {
|
|
||||||
logger.log("claimscorp-shopsToProcess-empty", "DEBUG", "api", null, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
|
|
||||||
|
|
||||||
await sendServerEmail({
|
|
||||||
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
|
|
||||||
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
|
||||||
allXMLResults.map((x) => ({
|
|
||||||
imexshopid: x.imexshopid,
|
|
||||||
filename: x.filename,
|
|
||||||
count: x.count,
|
|
||||||
result: x.result
|
|
||||||
})),
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log("claimscorp-end", "DEBUG", "api", null, null);
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("claimscorp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) {
|
|
||||||
for (const bodyshop of shopsToProcess) {
|
|
||||||
const erroredJobs = [];
|
|
||||||
try {
|
|
||||||
logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
shopname: bodyshop.shopname
|
shopname: bodyshop.shopname
|
||||||
});
|
});
|
||||||
|
const erroredJobs = [];
|
||||||
|
try {
|
||||||
|
const { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_QUERY, {
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||||
|
...(end && { end: moment(end).endOf("day") })
|
||||||
|
});
|
||||||
|
|
||||||
const { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_QUERY, {
|
const claimsCorpObject = {
|
||||||
bodyshopid: bodyshop.id,
|
DataFeed: {
|
||||||
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
ShopInfo: {
|
||||||
...(end && { end: moment(end).endOf("day") })
|
ShopID: bodyshops_by_pk.claimscorpid,
|
||||||
});
|
ShopName: bodyshops_by_pk.shopname,
|
||||||
|
RO: jobs.map((j) =>
|
||||||
const claimsCorpObject = {
|
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
||||||
DataFeed: {
|
erroredJobs.push({ job: job, error: error.toString() });
|
||||||
ShopInfo: {
|
})
|
||||||
ShopID: bodyshops_by_pk.claimscorpid,
|
)
|
||||||
ShopName: bodyshops_by_pk.shopname,
|
}
|
||||||
RO: jobs.map((j) =>
|
|
||||||
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
|
||||||
erroredJobs.push({ job: job, error: error.toString() });
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if (erroredJobs.length > 0) {
|
if (erroredJobs.length > 0) {
|
||||||
logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
|
logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||||
count: erroredJobs.length,
|
count: erroredJobs.length,
|
||||||
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = builder
|
||||||
|
.create(
|
||||||
|
{
|
||||||
|
// version: "1.0",
|
||||||
|
// encoding: "UTF-8",
|
||||||
|
//keepNullNodes: true,
|
||||||
|
},
|
||||||
|
claimsCorpObject
|
||||||
|
)
|
||||||
|
.end({ allowEmptyTags: true });
|
||||||
|
|
||||||
|
allxmlsToUpload.push({
|
||||||
|
count: claimsCorpObject.DataFeed.ShopInfo.RO.length,
|
||||||
|
xml: ret,
|
||||||
|
filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
|
shopname: bodyshop.shopname
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
//Error at the shop level.
|
||||||
|
logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, {
|
||||||
|
...error
|
||||||
|
});
|
||||||
|
|
||||||
|
allErrors.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: bodyshop.imexshopid,
|
||||||
|
claimscorpid: bodyshop.claimscorpid,
|
||||||
|
fatal: true,
|
||||||
|
errors: [error.toString()]
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
allErrors.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: bodyshop.imexshopid,
|
||||||
|
claimscorpid: bodyshop.claimscorpid,
|
||||||
|
errors: erroredJobs.map((ej) => ({
|
||||||
|
ro_number: ej.job?.ro_number,
|
||||||
|
jobid: ej.job?.id,
|
||||||
|
error: ej.error
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipUpload) {
|
||||||
|
for (const xmlObj of allxmlsToUpload) {
|
||||||
|
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(allxmlsToUpload);
|
||||||
|
sendServerEmail({
|
||||||
|
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
|
||||||
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
|
Uploaded: ${JSON.stringify(
|
||||||
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sftp = new Client();
|
||||||
|
sftp.on("error", (errors) =>
|
||||||
|
logger.log("claimscorp-sftp-error", "ERROR", "api", null, {
|
||||||
|
...errors
|
||||||
|
})
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
//Connect to the FTP and upload all.
|
||||||
|
|
||||||
|
await sftp.connect(ftpSetup);
|
||||||
|
|
||||||
|
for (const xmlObj of allxmlsToUpload) {
|
||||||
|
logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, {
|
||||||
|
filename: xmlObj.filename
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`);
|
||||||
|
logger.log("claimscorp-sftp-upload-result", "DEBUG", "api", null, {
|
||||||
|
uploadResult
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const xmlObj = {
|
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
xml: builder.create({}, claimsCorpObject).end({ allowEmptyTags: true }),
|
|
||||||
filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`,
|
|
||||||
count: claimsCorpObject.DataFeed.ShopInfo.RO.length
|
|
||||||
};
|
|
||||||
|
|
||||||
if (skipUpload) {
|
|
||||||
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
|
||||||
} else {
|
|
||||||
await uploadViaSFTP(xmlObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
allXMLResults.push({
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
claimscorpid: bodyshop.claimscorpid,
|
|
||||||
count: xmlObj.count,
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
result: xmlObj.result
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
|
||||||
shopname: bodyshop.shopname
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//Error at the shop level.
|
logger.log("claimscorp-sftp-error", "ERROR", "api", null, {
|
||||||
logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
|
...error
|
||||||
|
|
||||||
allErrors.push({
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
claimscorpid: bodyshop.claimscorpid,
|
|
||||||
fatal: true,
|
|
||||||
errors: [error.toString()]
|
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
allErrors.push({
|
sftp.end();
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
claimscorpid: bodyshop.claimscorpid,
|
|
||||||
errors: erroredJobs.map((ej) => ({
|
|
||||||
ro_number: ej.job?.ro_number,
|
|
||||||
jobid: ej.job?.id,
|
|
||||||
error: ej.error
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
sendServerEmail({
|
||||||
}
|
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
|
||||||
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
async function uploadViaSFTP(xmlObj) {
|
Uploaded: ${JSON.stringify(
|
||||||
const sftp = new Client();
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
sftp.on("error", (errors) =>
|
null,
|
||||||
logger.log("claimscorp-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, {
|
2
|
||||||
error: errors.message,
|
)}
|
||||||
stack: errors.stack
|
`
|
||||||
})
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
//Connect to the FTP and upload all.
|
|
||||||
await sftp.connect(ftpSetup);
|
|
||||||
|
|
||||||
try {
|
|
||||||
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
|
|
||||||
logger.log("claimscorp-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
|
|
||||||
imexshopid: xmlObj.imexshopid,
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
result: xmlObj.result
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("claimscorp-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("claimscorp-sftp-error", "ERROR", "api", xmlObj.bodyshopid, {
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack
|
|
||||||
});
|
});
|
||||||
throw error;
|
res.sendStatus(200);
|
||||||
} finally {
|
} catch (error) {
|
||||||
sftp.end();
|
res.status(200).json(error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const CreateRepairOrderTag = (job, errorCallback) => {
|
const CreateRepairOrderTag = (job, errorCallback) => {
|
||||||
//Level 2
|
//Level 2
|
||||||
@@ -467,7 +445,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
|||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, {
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
|
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,17 +16,15 @@ const { sendServerEmail } = require("../email/sendemail");
|
|||||||
const DineroFormat = "0,0.00";
|
const DineroFormat = "0,0.00";
|
||||||
const DateFormat = "MM/DD/YYYY";
|
const DateFormat = "MM/DD/YYYY";
|
||||||
|
|
||||||
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
const repairOpCodes = ["OP4", "OP9", "OP10"];
|
||||||
|
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
|
||||||
|
|
||||||
const ftpSetup = {
|
const ftpSetup = {
|
||||||
host: process.env.KAIZEN_HOST,
|
host: process.env.KAIZEN_HOST,
|
||||||
port: process.env.KAIZEN_PORT,
|
port: process.env.KAIZEN_PORT,
|
||||||
username: process.env.KAIZEN_USER,
|
username: process.env.KAIZEN_USER,
|
||||||
password: process.env.KAIZEN_PASSWORD,
|
password: process.env.KAIZEN_PASSWORD,
|
||||||
debug:
|
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
|
||||||
process.env.NODE_ENV !== "production"
|
|
||||||
? (message, ...data) => logger.log(message, "DEBUG", "api", null, data)
|
|
||||||
: () => {},
|
|
||||||
algorithms: {
|
algorithms: {
|
||||||
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
||||||
}
|
}
|
||||||
@@ -38,178 +36,167 @@ exports.default = async (req, res) => {
|
|||||||
res.sendStatus(403);
|
res.sendStatus(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only process if the appropriate token is provided.
|
|
||||||
|
//Query for the List of Bodyshop Clients.
|
||||||
|
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
||||||
|
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
||||||
|
|
||||||
|
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, {
|
||||||
|
imexshopid: kaizenShopsIDs
|
||||||
|
});
|
||||||
|
|
||||||
|
const specificShopIds = req.body.bodyshopIds; // ['uuid]
|
||||||
|
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||||
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
||||||
res.sendStatus(401);
|
res.sendStatus(401);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const allxmlsToUpload = [];
|
||||||
// Send immediate response and continue processing.
|
const allErrors = [];
|
||||||
res.status(202).json({
|
|
||||||
success: true,
|
|
||||||
message: "Processing request ...",
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) {
|
||||||
const allXMLResults = [];
|
|
||||||
const allErrors = [];
|
|
||||||
|
|
||||||
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { imexshopid: kaizenShopsIDs }); //Query for the List of Bodyshop Clients.
|
|
||||||
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
|
||||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
|
||||||
|
|
||||||
const shopsToProcess =
|
|
||||||
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
|
||||||
logger.log("kaizen-shopsToProcess-generated", "DEBUG", "api", null, null);
|
|
||||||
|
|
||||||
if (shopsToProcess.length === 0) {
|
|
||||||
logger.log("kaizen-shopsToProcess-empty", "DEBUG", "api", null, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
|
|
||||||
|
|
||||||
await sendServerEmail({
|
|
||||||
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
|
||||||
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
|
||||||
allXMLResults.map((x) => ({
|
|
||||||
imexshopid: x.imexshopid,
|
|
||||||
filename: x.filename,
|
|
||||||
count: x.count,
|
|
||||||
result: x.result
|
|
||||||
})),
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log("kaizen-end", "DEBUG", "api", null, null);
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("kaizen-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) {
|
|
||||||
for (const bodyshop of shopsToProcess) {
|
|
||||||
const erroredJobs = [];
|
|
||||||
try {
|
|
||||||
logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
shopname: bodyshop.shopname
|
shopname: bodyshop.shopname
|
||||||
});
|
});
|
||||||
|
const erroredJobs = [];
|
||||||
|
try {
|
||||||
|
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||||
|
...(end && { end: moment(end).endOf("day") })
|
||||||
|
});
|
||||||
|
|
||||||
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
const kaizenObject = {
|
||||||
bodyshopid: bodyshop.id,
|
DataFeed: {
|
||||||
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
ShopInfo: {
|
||||||
...(end && { end: moment(end).endOf("day") })
|
ShopName: bodyshops_by_pk.shopname,
|
||||||
});
|
Jobs: jobs.map((j) =>
|
||||||
|
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
||||||
const kaizenObject = {
|
erroredJobs.push({ job: job, error: error.toString() });
|
||||||
DataFeed: {
|
})
|
||||||
ShopInfo: {
|
)
|
||||||
ShopName: bodyshops_by_pk.shopname,
|
}
|
||||||
Jobs: jobs.map((j) =>
|
|
||||||
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
|
||||||
erroredJobs.push({ job: job, error: error.toString() });
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if (erroredJobs.length > 0) {
|
if (erroredJobs.length > 0) {
|
||||||
logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, {
|
logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||||
count: erroredJobs.length,
|
count: erroredJobs.length,
|
||||||
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = builder
|
||||||
|
.create(
|
||||||
|
{
|
||||||
|
// version: "1.0",
|
||||||
|
// encoding: "UTF-8",
|
||||||
|
//keepNullNodes: true,
|
||||||
|
},
|
||||||
|
kaizenObject
|
||||||
|
)
|
||||||
|
.end({ allowEmptyTags: true });
|
||||||
|
|
||||||
|
allxmlsToUpload.push({
|
||||||
|
count: kaizenObject.DataFeed.ShopInfo.Jobs.length,
|
||||||
|
xml: ret,
|
||||||
|
filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml`
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
|
shopname: bodyshop.shopname
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
//Error at the shop level.
|
||||||
|
logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, {
|
||||||
|
...error
|
||||||
|
});
|
||||||
|
|
||||||
|
allErrors.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: bodyshop.imexshopid,
|
||||||
|
shopname: bodyshop.shopname,
|
||||||
|
fatal: true,
|
||||||
|
errors: [error.toString()]
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
allErrors.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: bodyshop.imexshopid,
|
||||||
|
shopname: bodyshop.shopname,
|
||||||
|
errors: erroredJobs.map((ej) => ({
|
||||||
|
ro_number: ej.job?.ro_number,
|
||||||
|
jobid: ej.job?.id,
|
||||||
|
error: ej.error
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipUpload) {
|
||||||
|
for (const xmlObj of allxmlsToUpload) {
|
||||||
|
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(allxmlsToUpload);
|
||||||
|
sendServerEmail({
|
||||||
|
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
|
Uploaded: ${JSON.stringify(
|
||||||
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sftp = new Client();
|
||||||
|
sftp.on("error", (errors) =>
|
||||||
|
logger.log("kaizen-sftp-error", "ERROR", "api", null, {
|
||||||
|
...errors
|
||||||
|
})
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
//Connect to the FTP and upload all.
|
||||||
|
|
||||||
|
await sftp.connect(ftpSetup);
|
||||||
|
|
||||||
|
for (const xmlObj of allxmlsToUpload) {
|
||||||
|
logger.log("kaizen-sftp-upload", "DEBUG", "api", null, {
|
||||||
|
filename: xmlObj.filename
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`);
|
||||||
|
logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, {
|
||||||
|
uploadResult
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const xmlObj = {
|
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
xml: builder.create({}, kaizenObject).end({ allowEmptyTags: true }),
|
|
||||||
filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml`,
|
|
||||||
count: kaizenObject.DataFeed.ShopInfo.Jobs.length
|
|
||||||
};
|
|
||||||
|
|
||||||
if (skipUpload) {
|
|
||||||
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
|
||||||
} else {
|
|
||||||
await uploadViaSFTP(xmlObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
allXMLResults.push({
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
count: xmlObj.count,
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
result: xmlObj.result
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
|
||||||
shopname: bodyshop.shopname
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//Error at the shop level.
|
logger.log("kaizen-sftp-error", "ERROR", "api", null, {
|
||||||
logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
|
...error
|
||||||
|
|
||||||
allErrors.push({
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
shopname: bodyshop.shopname,
|
|
||||||
fatal: true,
|
|
||||||
errors: [error.toString()]
|
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
allErrors.push({
|
sftp.end();
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
imexshopid: bodyshop.imexshopid,
|
|
||||||
shopname: bodyshop.shopname,
|
|
||||||
errors: erroredJobs.map((ej) => ({
|
|
||||||
ro_number: ej.job?.ro_number,
|
|
||||||
jobid: ej.job?.id,
|
|
||||||
error: ej.error
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadViaSFTP(xmlObj) {
|
|
||||||
const sftp = new Client();
|
|
||||||
sftp.on("error", (errors) =>
|
|
||||||
logger.log("kaizen-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, {
|
|
||||||
error: errors.message,
|
|
||||||
stack: errors.stack
|
|
||||||
})
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
//Connect to the FTP and upload all.
|
|
||||||
await sftp.connect(ftpSetup);
|
|
||||||
|
|
||||||
try {
|
|
||||||
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
|
|
||||||
logger.log("kaizen-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
|
|
||||||
imexshopid: xmlObj.imexshopid,
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
result: xmlObj.result
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("kaizen-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
|
|
||||||
filename: xmlObj.filename,
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
sendServerEmail({
|
||||||
|
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
|
Uploaded: ${JSON.stringify(
|
||||||
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("kaizen-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack });
|
res.status(200).json(error);
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
sftp.end();
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const CreateRepairOrderTag = (job, errorCallback) => {
|
const CreateRepairOrderTag = (job, errorCallback) => {
|
||||||
//Level 2
|
//Level 2
|
||||||
@@ -433,7 +420,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
|||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("kaizen-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
logger.log("kaizen-job-calculate-error", "ERROR", "api", null, {
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
|
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const { isString, isEmpty } = require("lodash");
|
const { isString, isEmpty } = require("lodash");
|
||||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||||
const { default: InstanceManager } = require("../utils/instanceMgr");
|
const { InstanceRegion } = require("../utils/instanceMgr");
|
||||||
const aws = require("@aws-sdk/client-ses");
|
const aws = require("@aws-sdk/client-ses");
|
||||||
const nodemailer = require("nodemailer");
|
const nodemailer = require("nodemailer");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
@@ -10,12 +10,7 @@ const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.e
|
|||||||
const sesConfig = {
|
const sesConfig = {
|
||||||
apiVersion: "latest",
|
apiVersion: "latest",
|
||||||
credentials: defaultProvider(),
|
credentials: defaultProvider(),
|
||||||
region: isLocal
|
region: InstanceRegion()
|
||||||
? "ca-central-1"
|
|
||||||
: InstanceManager({
|
|
||||||
imex: "ca-central-1",
|
|
||||||
rome: "us-east-2"
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ require("dotenv").config({
|
|||||||
const domain = process.env.NODE_ENV ? "secure" : "test";
|
const domain = process.env.NODE_ENV ? "secure" : "test";
|
||||||
|
|
||||||
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
||||||
|
const { InstanceRegion } = require("../utils/instanceMgr");
|
||||||
|
|
||||||
const client = new SecretsManagerClient({
|
const client = new SecretsManagerClient({
|
||||||
region: InstanceManager({
|
region: InstanceRegion()
|
||||||
imex: "ca-central-1",
|
|
||||||
rome: "us-east-2"
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const gqlClient = require("../graphql-client/graphql-client").client;
|
const gqlClient = require("../graphql-client/graphql-client").client;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const { canvastest } = require("../render/canvas-handler");
|
|||||||
const { alertCheck } = require("../alerts/alertcheck");
|
const { alertCheck } = require("../alerts/alertcheck");
|
||||||
|
|
||||||
//Test route to ensure Express is responding.
|
//Test route to ensure Express is responding.
|
||||||
router.get("/test", async function (req, res) {
|
router.get("/test", eventAuthorizationMiddleware, async function (req, res) {
|
||||||
const commit = require("child_process").execSync("git rev-parse --short HEAD");
|
const commit = require("child_process").execSync("git rev-parse --short HEAD");
|
||||||
// console.log(app.get('trust proxy'));
|
// console.log(app.get('trust proxy'));
|
||||||
// console.log("remoteAddress", req.socket.remoteAddress);
|
// console.log("remoteAddress", req.socket.remoteAddress);
|
||||||
@@ -32,6 +32,32 @@ router.get("/test", async function (req, res) {
|
|||||||
res.status(200).send(`OK - ${commit}`);
|
res.status(200).send(`OK - ${commit}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/test-logs", eventAuthorizationMiddleware, (req, res) => {
|
||||||
|
const { logger } = req;
|
||||||
|
// // Test 1: Log with a message that exceeds the size limit, triggering an upload to S3.
|
||||||
|
const largeMessage = "A".repeat(256 * 1024 + 1); // Message larger than the log size limit
|
||||||
|
logger.log(largeMessage, "error", "user123", null, { detail: "large log entry" });
|
||||||
|
|
||||||
|
// Test 2: Log with a message that is within the size limit, should log directly using winston.
|
||||||
|
const smallMessage = "A small log message";
|
||||||
|
logger.log(smallMessage, "info", "user123", null, { detail: "small log entry" });
|
||||||
|
|
||||||
|
// Test 3: Log with the `upload` flag set to `true`, forcing the log to be uploaded to S3.
|
||||||
|
logger.log(
|
||||||
|
"This log will be uploaded to S3 regardless of size",
|
||||||
|
"warning",
|
||||||
|
"user123",
|
||||||
|
null,
|
||||||
|
{ detail: "upload log" },
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 4: Log with a message that doesn't exceed the size limit and doesn't require an upload.
|
||||||
|
logger.log("Normal log entry", "debug", "user123", { id: 4 }, { detail: "normal log entry" });
|
||||||
|
|
||||||
|
return res.status(500).send("Logs tested.");
|
||||||
|
});
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
router.post("/search", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, os.search);
|
router.post("/search", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, os.search);
|
||||||
router.post("/opensearch", eventAuthorizationMiddleware, os.handler);
|
router.post("/opensearch", eventAuthorizationMiddleware, os.handler);
|
||||||
|
|||||||
@@ -44,4 +44,10 @@ function InstanceManager({ args, instance, debug, executeFunction, rome, promana
|
|||||||
return propToReturn === undefined ? null : propToReturn;
|
return propToReturn === undefined ? null : propToReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.InstanceRegion = () =>
|
||||||
|
InstanceManager({
|
||||||
|
imex: "ca-central-1",
|
||||||
|
rome: "us-east-2"
|
||||||
|
});
|
||||||
|
|
||||||
exports.default = InstanceManager;
|
exports.default = InstanceManager;
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ const winston = require("winston");
|
|||||||
const WinstonCloudWatch = require("winston-cloudwatch");
|
const WinstonCloudWatch = require("winston-cloudwatch");
|
||||||
const { isString, isEmpty } = require("lodash");
|
const { isString, isEmpty } = require("lodash");
|
||||||
const { networkInterfaces, hostname } = require("node:os");
|
const { networkInterfaces, hostname } = require("node:os");
|
||||||
|
const { uploadFileToS3 } = require("./s3");
|
||||||
|
const { v4 } = require("uuid");
|
||||||
|
const { InstanceRegion } = require("./instanceMgr");
|
||||||
|
|
||||||
const LOG_LEVELS = {
|
const LOG_LEVELS = {
|
||||||
error: { level: 0, name: "error" },
|
error: { level: 0, name: "error" },
|
||||||
@@ -20,6 +23,30 @@ const LOG_LEVELS = {
|
|||||||
silly: { level: 6, name: "silly" }
|
silly: { level: 6, name: "silly" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LOG_LENGTH_LIMIT = 256 * 1024; // 256KB
|
||||||
|
|
||||||
|
const S3_BUCKET_NAME = InstanceManager({
|
||||||
|
imex: "imex-large-log",
|
||||||
|
rome: "rome-large-log"
|
||||||
|
});
|
||||||
|
|
||||||
|
const region = InstanceRegion();
|
||||||
|
|
||||||
|
const estimateLogSize = (logEntry) => {
|
||||||
|
let estimatedSize = 0;
|
||||||
|
for (const key in logEntry) {
|
||||||
|
if (logEntry.hasOwnProperty(key)) {
|
||||||
|
const value = logEntry[key];
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
estimatedSize += key.length; // Only count the key length if value is undefined or null
|
||||||
|
} else {
|
||||||
|
estimatedSize += key.length + (typeof value === "string" ? value.length : JSON.stringify(value).length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimatedSize;
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeLevel = (level) => (level ? level.toLowerCase() : LOG_LEVELS.debug.name);
|
const normalizeLevel = (level) => (level ? level.toLowerCase() : LOG_LEVELS.debug.name);
|
||||||
|
|
||||||
const createLogger = () => {
|
const createLogger = () => {
|
||||||
@@ -30,10 +57,7 @@ const createLogger = () => {
|
|||||||
const winstonCloudwatchTransportDefaults = {
|
const winstonCloudwatchTransportDefaults = {
|
||||||
logGroupName: logGroupName,
|
logGroupName: logGroupName,
|
||||||
awsOptions: {
|
awsOptions: {
|
||||||
region: InstanceManager({
|
region
|
||||||
imex: "ca-central-1",
|
|
||||||
rome: "us-east-2"
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
jsonMessage: true
|
jsonMessage: true
|
||||||
};
|
};
|
||||||
@@ -124,15 +148,66 @@ const createLogger = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = (message, type, user, record, meta) => {
|
const log = (message, type, user, record, meta, upload) => {
|
||||||
winstonLogger.log({
|
const logEntry = {
|
||||||
level: normalizeLevel(type),
|
level: normalizeLevel(type),
|
||||||
message,
|
message,
|
||||||
user,
|
user,
|
||||||
record,
|
record,
|
||||||
hostname: internalHostname,
|
hostname: internalHostname,
|
||||||
meta
|
meta
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const uploadLogToS3 = (logEntry, message, type, user) => {
|
||||||
|
const uniqueId = v4();
|
||||||
|
const dateTimeString = new Date().toISOString().replace(/:/g, "-");
|
||||||
|
const envName = process.env?.NODE_ENV ? process.env.NODE_ENV : "";
|
||||||
|
const logStreamName = `${envName}-${internalHostname}-${dateTimeString}-${uniqueId}.json`;
|
||||||
|
const logString = JSON.stringify(logEntry);
|
||||||
|
const webPath = isLocal
|
||||||
|
? `https://${S3_BUCKET_NAME}.s3.localhost.localstack.cloud:4566/${logStreamName}`
|
||||||
|
: `https://${S3_BUCKET_NAME}.s3.${region}.amazonaws.com/${logStreamName}`;
|
||||||
|
|
||||||
|
uploadFileToS3({ bucketName: S3_BUCKET_NAME, key: logStreamName, content: logString })
|
||||||
|
.then(() => {
|
||||||
|
log("A log file has been uploaded to S3", "info", "S3", null, {
|
||||||
|
logStreamName,
|
||||||
|
webPath,
|
||||||
|
message: message?.slice(0, 200),
|
||||||
|
type,
|
||||||
|
user
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log("Error in S3 Upload", "error", "S3", null, {
|
||||||
|
logStreamName,
|
||||||
|
webPath,
|
||||||
|
message: message?.slice(0, 100),
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
errorMessage: err?.message?.slice(0, 100)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkAndUploadLog = () => {
|
||||||
|
const estimatedSize = estimateLogSize(logEntry);
|
||||||
|
|
||||||
|
if (estimatedSize > LOG_LENGTH_LIMIT * 0.9 || estimatedSize > LOG_LENGTH_LIMIT) {
|
||||||
|
uploadLogToS3(logEntry, message, type, user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// Upload log immediately if upload is true, otherwise check the log size.
|
||||||
|
if (upload) {
|
||||||
|
uploadLogToS3(logEntry, message, type, user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkAndUploadLog()) return;
|
||||||
|
|
||||||
|
winstonLogger.log(logEntry);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
109
server/utils/s3.js
Normal file
109
server/utils/s3.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
const {
|
||||||
|
S3Client,
|
||||||
|
PutObjectCommand,
|
||||||
|
GetObjectCommand,
|
||||||
|
ListObjectsV2Command,
|
||||||
|
DeleteObjectCommand,
|
||||||
|
CopyObjectCommand
|
||||||
|
} = require("@aws-sdk/client-s3");
|
||||||
|
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||||
|
const { InstanceRegion } = require("./instanceMgr");
|
||||||
|
const { isString, isEmpty } = require("lodash");
|
||||||
|
|
||||||
|
const createS3Client = () => {
|
||||||
|
const S3Options = {
|
||||||
|
region: InstanceRegion(),
|
||||||
|
credentials: defaultProvider()
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||||
|
|
||||||
|
if (isLocal) {
|
||||||
|
S3Options.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||||
|
S3Options.forcePathStyle = true; // Needed for LocalStack to avoid bucket name as hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3Client = new S3Client(S3Options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a file to the specified S3 bucket and key.
|
||||||
|
*/
|
||||||
|
const uploadFileToS3 = async ({ bucketName, key, content, contentType }) => {
|
||||||
|
const params = {
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: key,
|
||||||
|
Body: content,
|
||||||
|
ContentType: contentType ?? "application/json"
|
||||||
|
};
|
||||||
|
const command = new PutObjectCommand(params);
|
||||||
|
return await s3Client.send(command);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a file from the specified S3 bucket and key.
|
||||||
|
*/
|
||||||
|
const downloadFileFromS3 = async ({ bucketName, key }) => {
|
||||||
|
const params = { Bucket: bucketName, Key: key };
|
||||||
|
const command = new GetObjectCommand(params);
|
||||||
|
const data = await s3Client.send(command);
|
||||||
|
return data.Body;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists objects in the specified S3 bucket.
|
||||||
|
*/
|
||||||
|
const listFilesInS3Bucket = async (bucketName, prefix = "") => {
|
||||||
|
const params = { Bucket: bucketName, Prefix: prefix };
|
||||||
|
const command = new ListObjectsV2Command(params);
|
||||||
|
const data = await s3Client.send(command);
|
||||||
|
return data.Contents || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a file from the specified S3 bucket and key.
|
||||||
|
*/
|
||||||
|
const deleteFileFromS3 = async ({ bucketName, key }) => {
|
||||||
|
const params = { Bucket: bucketName, Key: key };
|
||||||
|
const command = new DeleteObjectCommand(params);
|
||||||
|
return await s3Client.send(command);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a file within S3 from a source bucket/key to a destination bucket/key.
|
||||||
|
*/
|
||||||
|
const copyFileInS3 = async ({ sourceBucket, sourceKey, destinationBucket, destinationKey }) => {
|
||||||
|
const params = {
|
||||||
|
CopySource: `/${sourceBucket}/${sourceKey}`,
|
||||||
|
Bucket: destinationBucket,
|
||||||
|
Key: destinationKey
|
||||||
|
};
|
||||||
|
const command = new CopyObjectCommand(params);
|
||||||
|
return await s3Client.send(command);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a file exists in the specified S3 bucket and key.
|
||||||
|
*/
|
||||||
|
const fileExistsInS3 = async ({ bucketName, key }) => {
|
||||||
|
try {
|
||||||
|
await downloadFileFromS3({ bucketName, key });
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === "NoSuchKey" || error.name === "NotFound") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
uploadFileToS3,
|
||||||
|
downloadFileFromS3,
|
||||||
|
listFilesInS3Bucket,
|
||||||
|
deleteFileFromS3,
|
||||||
|
copyFileInS3,
|
||||||
|
fileExistsInS3
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = createS3Client();
|
||||||
@@ -155,10 +155,17 @@ function createJsonEvent(socket, level, message, json) {
|
|||||||
message
|
message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
logger.log("ws-log-event-json", level, socket.user.email, socket.recordid, {
|
logger.log(
|
||||||
wsmessage: message,
|
"ws-log-event-json",
|
||||||
json
|
level,
|
||||||
});
|
socket.user.email,
|
||||||
|
socket.recordid,
|
||||||
|
{
|
||||||
|
wsmessage: message,
|
||||||
|
json
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||||
socket.logEvents.push({
|
socket.logEvents.push({
|
||||||
@@ -189,7 +196,8 @@ function createXmlEvent(socket, xml, message, isError = false) {
|
|||||||
{
|
{
|
||||||
wsmessage: message,
|
wsmessage: message,
|
||||||
xml
|
xml
|
||||||
}
|
},
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user