Compare commits

...

14 Commits

Author SHA1 Message Date
Dave
8a4679f86c feature/IO-3638-Reynolds-OpenSearch - Add Search on DMS id in Reynolds shops 2026-04-09 11:14:17 -04:00
Dave Richer
a4dbc5250e Merged in release/2026-04-03 (pull request #3179)
Release/2026-04-03 - IO-1366, IO-3356, IO-3515, IO-3587, IO-3599, IO-3609, IO-3616, IO-3622, IO-3623, IO-3627, IO-3629, IO-3637
2026-04-03 01:46:11 +00:00
Allan Carr
a1d0e2df93 Merged in feature/IO-3637-DMS-ID-Production-Board-Column (pull request #3175)
IO-3637 DMS ID Production Board Column

Approved-by: Dave Richer
Approved-by: Patrick Fic
2026-04-03 01:32:25 +00:00
Allan Carr
9a86a337bb IO-3637 DMS ID Production Board Column
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-04-02 15:50:56 -07:00
Dave
7688f22161 release/2026-04-03 - Clean up localstack endpoints / env check 2026-04-01 14:39:34 -04:00
Allan Carr
efdcd06921 Merged in feature/IO-1366-Re-export-Bill-Audit-Log (pull request #3170)
IO-1366 Bill Reexport Audit Log

Approved-by: Dave Richer
2026-03-31 20:18:44 +00:00
Allan Carr
c0a37d7c1a IO-1366 Bill Reexport Audit Log
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-31 09:50:08 -07:00
Dave
6759bc5865 release/2026-04-03 - Remove console.dir 2026-03-30 17:24:42 -04:00
Patrick Fic
04732fc6cd Merged in feature/IO-3356-rc-ford-ro-updates (pull request #3166)
IO-3356 Add CSR and Shop values per Grant @ RC Ford.

Approved-by: Dave Richer
2026-03-30 19:08:16 +00:00
Patrick Fic
a65a34ef1f Merged in feature/IO-3515-maintain-discount-on-ai-vendor (pull request #3167)
IO-3515 Retain discount application when AI vendor added.

Approved-by: Dave Richer
2026-03-30 19:07:43 +00:00
Patrick Fic
1ea7798eeb IO-3515 Retain discount application when AI vendor added. 2026-03-30 11:59:08 -07:00
Patrick Fic
7739d48741 IO-3356 Add CSR and Shop values per Grant @ RC Ford. 2026-03-30 11:44:38 -07:00
Allan Carr
074be66b8c Merged in feature/IO-3629-PostBatchWip-rtn-1-Catch (pull request #3163)
IO-3629 PostBatchWip Rtn != 0 error

Approved-by: Dave Richer
2026-03-30 15:07:15 +00:00
Dave Richer
c2d8d78e0a Merged in hotfix/2026-03-27 (pull request #3162)
Hotfix/2026 03 27
2026-03-27 19:16:16 +00:00
24 changed files with 214 additions and 89 deletions

View File

@@ -52,6 +52,7 @@ export function BillFormComponent({
const [discount, setDiscount] = useState(0);
const notification = useNotification();
const jobIdFormWatch = Form.useWatch("jobid", form);
const vendorIdFormWatch = Form.useWatch("vendorid", form);
const {
treatments: { Extended_Bill_Posting, ClosingPeriod }
@@ -118,6 +119,7 @@ export function BillFormComponent({
}
}, [
form,
vendorIdFormWatch,
billEdit,
loadOutstandingReturns,
loadInventory,

View File

@@ -9,18 +9,20 @@ import { createStructuredSelector } from "reselect";
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel
});
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
});
export default connect(mapStateToProps, mapDispatchToProps)(BillMarkForReexportButton);
export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
export function BillMarkForReexportButton({ bodyshop, authLevel, bill, insertAuditTrail }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const notification = useNotification();
@@ -47,6 +49,12 @@ export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
notification.success({
title: t("bills.successes.reexport")
});
insertAuditTrail({
jobid: bill.jobid,
billid: bill.id,
operation: AuditTrailMapping.billmarkforreexport(bill.invoice_number),
type: "billmarkforreexport"
});
} else {
notification.error({
title: t("bills.errors.saving", {

View File

@@ -58,6 +58,7 @@ export function ProductionColumnsComponent({
const columnKeys = columns.map((i) => i.key);
const cols = dataSource({
bodyshop,
technician,
data,
state: tableState,

View File

@@ -609,7 +609,19 @@ const productionListColumnsData = ({ technician, state, activeStatuses, data, bo
ellipsis: true,
render: (text, record) => <TimeFormatter>{record.date_repairstarted}</TimeFormatter>
}
},
...(bodyshop && bodyshop.rr_dealerid
? [
{
title: i18n.t("jobs.fields.dms.id"),
dataIndex: "dms_id",
key: "dms_id",
ellipsis: true,
sorter: (a, b) => alphaSort(a.dms_id, b.dms_id),
sortOrder: state.sortedInfo.columnKey === "dms_id" && state.sortedInfo.order
}
]
: []),
];
};
export default productionListColumnsData;

View File

@@ -244,6 +244,7 @@ export function ProductionListConfigManager({
nextConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state: ensureDefaultState(state),
refetch,
@@ -270,6 +271,7 @@ export function ProductionListConfigManager({
activeConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state: ensureDefaultState(state),
refetch,

View File

@@ -197,6 +197,7 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
employee_prep
employee_csr
date_repairstarted
dms_id
joblines_status {
part_type
status
@@ -269,6 +270,7 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
employee_prep
employee_csr
date_repairstarted
dms_id
joblines_status {
part_type
status
@@ -2671,6 +2673,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
suspended
job_totals
date_repairstarted
dms_id
joblines_status {
part_type
status

View File

@@ -120,6 +120,7 @@
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
"assignedlinehours": "Assigned job lines totaling {{hours}} units to {{team}}.",
"billdeleted": "Bill with invoice number {{invoice_number}} deleted.",
"billmarkforreexport": "Bill with invoice number {{invoice_number}} marked for re-export.",
"billposted": "Bill with invoice number {{invoice_number}} posted.",
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
"failedpayment": "Failed payment attempt.",

View File

@@ -121,6 +121,7 @@
"assignedlinehours": "",
"billdeleted": "",
"billposted": "",
"billmarkforreexport": "",
"billupdated": "",
"failedpayment": "",
"jobassignmentchange": "",

View File

@@ -120,6 +120,7 @@
"appointmentinsert": "",
"assignedlinehours": "",
"billdeleted": "",
"billmarkforreexport": "",
"billposted": "",
"billupdated": "",
"failedpayment": "",

View File

@@ -8,6 +8,7 @@ const AuditTrailMapping = {
appointmentcancel: (lost_sale_reason) => i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
appointmentinsert: (start) => i18n.t("audit_trail.messages.appointmentinsert", { start }),
billdeleted: (invoice_number) => i18n.t("audit_trail.messages.billdeleted", { invoice_number }),
billmarkforreexport: (invoice_number) => i18n.t("audit_trail.messages.billmarkforreexport", { invoice_number }),
billposted: (invoice_number) => i18n.t("audit_trail.messages.billposted", { invoice_number }),
billupdated: (invoice_number) => i18n.t("audit_trail.messages.billupdated", { invoice_number }),
jobassignmentchange: (operation, name) => i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),

View File

@@ -32,6 +32,7 @@ async function OpenSearchUpdateHandler(req, res) {
clm_no
clm_total
comment
dms_id
ins_co_nm
owner_owing
ownr_co_nm

View File

@@ -787,7 +787,8 @@ async function RepairOrderChange(socket) {
// "DatePromisedUTC": "0001-01-01T00:00:00.0000000Z",
DateVehicleCompleted: socket.JobData.actual_completion,
// "DateCustomerNotified": "0001-01-01T00:00:00.0000000Z",
// "CSR": "String",
"CSR": "IMEX", //Hardcoded for now as the only shop that uses this RO posting is RC and they paid for a custom build.
Shop: "RB",
// "CSRRef": "00000000000000000000000000000000",
// "BookingUser": "String",
// "BookingUserRef": "00000000000000000000000000000000",

View File

@@ -1,20 +1,17 @@
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { isString, isEmpty } = require("lodash");
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
const CHATTER_BASE_URL = process.env.CHATTER_API_BASE_URL || "https://api.chatterresearch.com";
const AWS_REGION = process.env.AWS_REGION || "ca-central-1";
// Configure SecretsManager client with localstack support
const secretsClientOptions = {
region: AWS_REGION,
region: InstanceRegion(),
credentials: defaultProvider()
};
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
if (isLocal) {
secretsClientOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
if (InstanceIsLocalStackEnabled()) {
secretsClientOptions.endpoint = InstanceLocalStackEndpoint();
}
const secretsClient = new SecretsManagerClient(secretsClientOptions);

View File

@@ -33,8 +33,6 @@ const createLocation = async (req, res) => {
const { logger } = req;
const { bodyshopID, googlePlaceID } = req.body;
console.dir({ body: req.body });
if (!DEFAULT_COMPANY_ID) {
logger.log("chatter-create-location-no-default-company", "warn", null, null, { bodyshopID });
return res.json({ success: false, message: "No default company set" });

View File

@@ -3,7 +3,7 @@ const Dinero = require("dinero.js");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default;
const { isString, isEmpty } = require("lodash");
const { InstanceIsLocalStackEnabled } = require("../utils/instanceMgr");
const fs = require("fs");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail, sendMexicoBillingEmail } = require("../email/sendemail");
@@ -35,10 +35,9 @@ const S3_BUCKET_NAME = InstanceManager({
rome: "rome-carfax-uploads"
});
const region = InstanceManager.InstanceRegion;
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
const uploadToS3 = (jsonObj, bucketName = S3_BUCKET_NAME) => {
const webPath = isLocal
const webPath = InstanceIsLocalStackEnabled()
? `https://${bucketName}.s3.localhost.localstack.cloud:4566/${jsonObj.filename}`
: `https://${bucketName}.s3.${region}.amazonaws.com/${jsonObj.filename}`;

View File

@@ -5,7 +5,8 @@ const logger = require("../utils/logger");
const fs = require("fs");
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { isString, isEmpty } = require("lodash");
const { InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
@@ -151,10 +152,8 @@ async function getPrivateKey() {
credentials: defaultProvider()
};
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
if (isLocal) {
secretsClientOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
if (InstanceIsLocalStackEnabled()) {
secretsClientOptions.endpoint = InstanceLocalStackEndpoint();
}
const client = new SecretsManagerClient(secretsClientOptions);

View File

@@ -1,20 +1,17 @@
const { isString, isEmpty } = require("lodash");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { InstanceRegion } = require("../utils/instanceMgr");
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
const aws = require("@aws-sdk/client-ses");
const nodemailer = require("nodemailer");
const logger = require("../utils/logger");
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
const sesConfig = {
apiVersion: "latest",
credentials: defaultProvider(),
region: InstanceRegion()
};
if (isLocal) {
sesConfig.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
if (InstanceIsLocalStackEnabled()) {
sesConfig.endpoint = InstanceLocalStackEndpoint();
logger.logger.debug(`SES Mailer set to LocalStack end point: ${sesConfig.endpoint}`);
}

View File

@@ -2442,6 +2442,9 @@ exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) {
id
shopid
bodyshop {
rr_dealerid
}
}
}`;

View File

@@ -4,6 +4,7 @@ const queries = require("../graphql-client/queries");
const client = require("../graphql-client/graphql-client").client;
const { pick, isNil } = require("lodash");
const { getClient } = require("../../libs/awsUtils");
const { JOB_DOCUMENT_FIELDS, getGlobalSearchQueryStringFields } = require("./os-search-config");
async function OpenSearchUpdateHandler(req, res) {
try {
@@ -21,27 +22,7 @@ async function OpenSearchUpdateHandler(req, res) {
switch (req.body.table.name) {
case "jobs":
document = pick(req.body.event.data.new, [
"id",
"bodyshopid",
"clm_no",
"clm_total",
"comment",
"ins_co_nm",
"owner_owing",
"ownr_co_nm",
"ownr_fn",
"ownr_ln",
"ownr_ph1",
"ownr_ph2",
"plate_no",
"ro_number",
"status",
"v_model_yr",
"v_make_desc",
"v_model_desc",
"v_vin"
]);
document = pick(req.body.event.data.new, JOB_DOCUMENT_FIELDS);
document.bodyshopid = req.body.event.data.new.shopid;
break;
case "vehicles":
@@ -197,15 +178,18 @@ async function OpenSearchSearchHandler(req, res) {
user: req.user.email
});
if (assocs.length === 0) {
if (assocs.associations.length === 0) {
res.sendStatus(401);
return;
}
const osClient = await getClient();
const activeAssociation = assocs.associations[0];
const bodyShopIdMatchOverride = isNil(process.env.BODY_SHOP_ID_MATCH_OVERRIDE)
? assocs.associations[0].shopid
? activeAssociation.shopid
: process.env.BODY_SHOP_ID_MATCH_OVERRIDE;
const isReynoldsEnabled = Boolean(activeAssociation.bodyshop?.rr_dealerid);
const { body } = await osClient.search({
...(index ? { index } : { index: ["jobs", "vehicles", "owners", "bills", "payments"] }),
@@ -241,21 +225,8 @@ async function OpenSearchSearchHandler(req, res) {
query: `*${search}*`,
// Weighted Fields
fields: [
"*ro_number^20",
"*clm_no^14",
"*v_vin^12",
"*plate_no^12",
"*ownr_ln^10",
"transactionid^10",
"paymentnum^10",
"invoice_number^10",
"*ownr_fn^8",
"*ownr_co_nm^8",
"*ownr_ph1^8",
"*ownr_ph2^8",
"*vendor.name^8",
"*comment^6"
// "*"
...getGlobalSearchQueryStringFields({ isReynoldsEnabled })
// "*"
]
}
}

View File

@@ -0,0 +1,69 @@
/**
* Fields to be included in the job document indexed in OpenSearch. These fields are used for both indexing and
* searching.
* @type {string[]}
*/
const JOB_DOCUMENT_FIELDS = [
"id",
"bodyshopid",
"clm_no",
"clm_total",
"comment",
"dms_id",
"ins_co_nm",
"owner_owing",
"ownr_co_nm",
"ownr_fn",
"ownr_ln",
"ownr_ph1",
"ownr_ph2",
"plate_no",
"ro_number",
"status",
"v_model_yr",
"v_make_desc",
"v_model_desc",
"v_vin"
];
/**
* Fields to be included in the global search query string. These fields are used for constructing the search query.
* @type {string[]}
*/
const BASE_GLOBAL_SEARCH_QUERY_STRING_FIELDS = [
"*ro_number^20",
"*clm_no^14",
"*v_vin^12",
"*plate_no^12",
"*ownr_ln^10",
"transactionid^10",
"paymentnum^10",
"invoice_number^10",
"*ownr_fn^8",
"*ownr_co_nm^8",
"*ownr_ph1^8",
"*ownr_ph2^8",
"*vendor.name^8",
"*comment^6"
];
/**
* Returns the fields to be included in the global search query string. If Reynolds is enabled, it includes the dms_id
* field with a higher boost.
* @param param0
* @param param0.isReynoldsEnabled
* @returns {string[]}
*/
const getGlobalSearchQueryStringFields = ({ isReynoldsEnabled = false } = {}) => {
if (!isReynoldsEnabled) {
return BASE_GLOBAL_SEARCH_QUERY_STRING_FIELDS;
}
return ["*dms_id^20", ...BASE_GLOBAL_SEARCH_QUERY_STRING_FIELDS];
};
module.exports = {
JOB_DOCUMENT_FIELDS,
BASE_GLOBAL_SEARCH_QUERY_STRING_FIELDS,
getGlobalSearchQueryStringFields
};

View File

@@ -0,0 +1,21 @@
import { describe, expect, it } from "vitest";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const { JOB_DOCUMENT_FIELDS, BASE_GLOBAL_SEARCH_QUERY_STRING_FIELDS, getGlobalSearchQueryStringFields } = require(
"../os-search-config"
);
describe("os-search-config", () => {
it("indexes dms_id on job documents", () => {
expect(JOB_DOCUMENT_FIELDS).toContain("dms_id");
});
it("includes dms_id in global search fields for Reynolds shops", () => {
expect(getGlobalSearchQueryStringFields({ isReynoldsEnabled: true })).toContain("*dms_id^20");
});
it("keeps the default search fields unchanged for non-Reynolds shops", () => {
expect(getGlobalSearchQueryStringFields()).toEqual(BASE_GLOBAL_SEARCH_QUERY_STRING_FIELDS);
});
});

View File

@@ -7,14 +7,24 @@
* @property { string | object | function } promanager Return this prop if Rome.
* @property { string | object | function } imex Return this prop if Rome.
*/
const { isString, isEmpty } = require("lodash");
function InstanceManager({ args, instance, debug, executeFunction, rome, promanager, imex }) {
/**
* InstanceManager is a utility function that determines which property to return based on the current instance type.
* @param param0
* @param param0.args
* @param param0.instance
* @param param0.debug
* @param param0.executeFunction
* @param param0.rome
* @param param0.promanager
* @param param0.imex
* @returns {*|null}
* @constructor
*/
const InstanceManager = ({ args, instance, debug, executeFunction, rome, promanager, imex }) => {
let propToReturn = null;
//TODO: Remove after debugging.
if (promanager) {
console.trace("ProManager Prop was used");
}
switch (instance || process.env.INSTANCE) {
case "IMEX":
propToReturn = imex;
@@ -50,15 +60,42 @@ function InstanceManager({ args, instance, debug, executeFunction, rome, promana
}
if (executeFunction && typeof propToReturn === "function") return propToReturn(...args);
return propToReturn === undefined ? null : propToReturn;
}
};
exports.InstanceRegion = () =>
/**
* Returns the AWS region to be used for the current instance, which is determined by the INSTANCE environment variable.
* @returns {*}
* @constructor
*/
const InstanceRegion = () =>
InstanceManager({
imex: "ca-central-1",
rome: "us-east-2"
});
exports.InstanceEndpoints = () =>
/**
* Checks if the instance is configured to use LocalStack by verifying the presence of the LOCALSTACK_HOSTNAME
* environment variable.
* @returns {boolean}
* @constructor
*/
const InstanceIsLocalStackEnabled = () =>
isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
/**
* Returns the LocalStack endpoint URL based on the LOCALSTACK_HOSTNAME environment variable.
* @returns {`http://${*}:4566`}
* @constructor
*/
const InstanceLocalStackEndpoint = () => `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
/**
* Returns the appropriate endpoints for the current instance, which can be used for making API calls or other network
* requests.
* @returns {*|null}
* @constructor
*/
const InstanceEndpoints = () =>
InstanceManager({
imex:
process.env?.NODE_ENV === "development"
@@ -74,4 +111,11 @@ exports.InstanceEndpoints = () =>
: "https://romeonline.io"
});
exports.default = InstanceManager;
module.exports = {
InstanceManager,
InstanceRegion,
InstanceIsLocalStackEnabled,
InstanceLocalStackEndpoint,
InstanceEndpoints,
default: InstanceManager
};

View File

@@ -2,10 +2,9 @@
const InstanceManager = require("../utils/instanceMgr").default;
const winston = require("winston");
const WinstonCloudWatch = require("winston-cloudwatch");
const { isString, isEmpty } = require("lodash");
const { uploadFileToS3 } = require("./s3");
const { v4 } = require("uuid");
const { InstanceRegion } = require("./instanceMgr");
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("./instanceMgr");
const getHostNameOrIP = require("./getHostNameOrIP");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
@@ -48,7 +47,7 @@ const normalizeLevel = (level) => (level ? level.toLowerCase() : LOG_LEVELS.debu
const createLogger = () => {
try {
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
const isLocal = InstanceIsLocalStackEnabled();
const logGroupName = isLocal ? "development" : process.env.CLOUDWATCH_LOG_GROUP;
const winstonCloudwatchTransportDefaults = {
@@ -60,7 +59,7 @@ const createLogger = () => {
};
if (isLocal) {
winstonCloudwatchTransportDefaults.awsOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
winstonCloudwatchTransportDefaults.awsOptions.endpoint = InstanceLocalStackEndpoint();
}
const levelFilter = (levels) => {

View File

@@ -7,8 +7,7 @@ const {
CopyObjectCommand
} = require("@aws-sdk/client-s3");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { InstanceRegion } = require("./instanceMgr");
const { isString, isEmpty } = require("lodash");
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("./instanceMgr");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const createS3Client = () => {
@@ -17,10 +16,8 @@ const createS3Client = () => {
credentials: defaultProvider()
};
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
if (isLocal) {
S3Options.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
if (InstanceIsLocalStackEnabled()) {
S3Options.endpoint = InstanceLocalStackEndpoint();
S3Options.forcePathStyle = true; // Needed for LocalStack to avoid bucket name as hostname
}
@@ -105,7 +102,7 @@ const createS3Client = () => {
});
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 360 });
return presignedUrl;
}
};
return {
uploadFileToS3,
@@ -119,7 +116,4 @@ const createS3Client = () => {
};
};
module.exports = createS3Client();