diff --git a/databindingsHandler.js b/databindingsHandler.js index 8861a7a..c14b2cc 100644 --- a/databindingsHandler.js +++ b/databindingsHandler.js @@ -40,6 +40,7 @@ async function fetchDataFromSources(dataSources, params) { let updatedParams = updateParams(source.params || {}, params, data); const response = await readJsonFormApi(source, { ...params, ...updatedParams }); + console.log("Response:",response); data[source.name] = response; } catch (error) { @@ -187,6 +188,7 @@ async function readJsonFormApi(datasource, pathParams) { const headers = { Authorization: `Bearer ${grant.id_token.token}`, "X-ICM-TrustedUsername": username, + "X-API-Host": apiHost, } if (type.toUpperCase() === 'GET') { // For GET requests, add params directly in axios config @@ -199,6 +201,7 @@ async function readJsonFormApi(datasource, pathParams) { } // Store response data + console.log("Response:",response); return ensureObjectOrArray(response.data); } catch (error) { @@ -210,6 +213,10 @@ async function readJsonFormApi(datasource, pathParams) { function buildUrlWithParams(host, endpoint, pathVariables) { const hostFromEnv = getHost(pathVariables,host); const endpointFromEnv = getEndpoint(endpoint); + + if (!hostFromEnv) throw new Error("API host not resolved"); + if (!endpointFromEnv) throw new Error("API endpoint not resolved"); + let url = `${hostFromEnv}${endpointFromEnv}`; // Replace any placeholder variables like @@attachmentId Object.keys(pathVariables).forEach(key => { @@ -222,18 +229,23 @@ function buildUrlWithParams(host, endpoint, pathVariables) { function getHost(params, host) { - // Use host from environment variable if available, otherwise fall back to JSON - try { - return params["apiHost"]; - } catch { - return process.env[host]; + // Use process.env if present + const fromEnv = resolveMaybeEnv(host); + if (fromEnv) return fromEnv; + + //Fall back to path params + if (params && typeof params.apiHost === "string" && params.apiHost.trim()) { + return params.apiHost.trim(); } + + return host ? (process.env[host] ?? host) : null; } function getEndpoint(endpoint) { // Use endpoint from environment variable if available, otherwise fall back to JSON - return process.env[endpoint] || endpoint; + return resolveMaybeEnv(endpoint) ?? endpoint; } + function buildBodyWithParams(bodyFromJson, pathVariables) { let bodyString = JSON.stringify(bodyFromJson); @@ -316,5 +328,25 @@ function updateParams(params, pathParams = {}, allFetchedData = {}) { return updated; } +function resolveMaybeEnv(str) { + if (typeof str !== "string" || !str) { + return null; + } + + // Check if value is in the form "process.env.VAR_NAME" + const envPattern = /^process\.env\.(.+)$/; + const match = str.match(envPattern); + + if (!match) { + return str; + } + + const envVarName = match[1]; + const envValue = process.env[envVarName]; + + return envValue != null ? envValue : str; +} + + module.exports.populateDatabindings = populateDatabindings; module.exports.buildUrlWithParams = buildUrlWithParams; diff --git a/icmJsonClobHandler.js b/icmJsonClobHandler.js new file mode 100644 index 0000000..a17dcd3 --- /dev/null +++ b/icmJsonClobHandler.js @@ -0,0 +1,99 @@ +const axios = require("axios"); +const { keycloakForSiebel } = require("./keycloak.js"); + +function parseIcmJsonClob(item) { + if (item.ICMJsonClob && typeof item.ICMJsonClob === 'string') { + try { + item.ICMJsonClobParsed = JSON.parse(item.ICMJsonClob); + delete item.ICMJsonClob; + } catch (error) { + console.warn(`Failed to parse ICMJsonClob for item ${item.Id || 'unknown'}: ${error.message}`); + item.ICMJsonClobParsed = null; + } + } + return item; +} + +function processIcmJsonClobData(jsonData) { + + // Create a copy to avoid mutating the original instance + const processedData = JSON.parse(JSON.stringify(jsonData)); + + if (processedData.items && Array.isArray(processedData.items)) { + processedData.items = processedData.items.map(parseIcmJsonClob); + return processedData; + } + + return parseIcmJsonClob(processedData); +} + +async function fetchIcmJsonClobData(attachmentId, apiHost) { + try { + const grant = await keycloakForSiebel.grantManager.obtainFromClientCredentials(); + + const username = process.env.TRUSTED_USERNAME; + + const headers = { + Authorization: `Bearer ${grant.id_token.token}`, + "X-ICM-TrustedUsername": username, + "Content-Type": "application/json" + }; + + const url = `${apiHost}${process.env.ICM_JSON_CLOB_ENDPONT}`; + + const queryParams = { + ViewMode: "Catalog", + SearchSpec: `UserFieldCLOB=\"${attachmentId}\"` + }; + + return await axios.get(url, { + headers, + params: queryParams, + timeout: 30000 + }); + + } catch (error) { + throw new Error(`Failed to fetch ICM data for SR ID ${attachmentId}: ${error.message}`); + } +} + +function extractICMJsonClobParsed(data) { + if (data.items && Array.isArray(data.items)) { + return data.items + .map(item => item.ICMJsonClobParsed) + .filter(parsed => parsed !== null && parsed !== undefined); + } + + if (data.ICMJsonClobParsed !== null && data.ICMJsonClobParsed !== undefined) { + return data.ICMJsonClobParsed; + } + + return []; +} + +async function getProcessedData(attachmentId, apiHost, returnAnswersOnly = true) { + try { + const response = await fetchIcmJsonClobData(attachmentId, apiHost); + let processedResult = processIcmJsonClobData(response.data); + + if (returnAnswersOnly) { + processedResult = extractICMJsonClobParsed(processedResult); + } + + return { + success: true, + data: processedResult + }; + } catch (error) { + console.error("API call failed:", error.message); + + return { + success: false, + error: error.message + }; + } +} + +module.exports = { + getProcessedData +}; \ No newline at end of file diff --git a/routes.js b/routes.js index 252d79d..0cbd7f2 100644 --- a/routes.js +++ b/routes.js @@ -6,19 +6,39 @@ const { generateTemplate,generateNewTemplate } = require("./generateHandler"); const { saveICMdata, loadICMdata, clearICMLockedFlag } = require("./saveICMdataHandler"); const { getUsername } = require("./usernameHandler.js"); const renderRouter = require("./renderHandler"); +const appCfg = require('./appConfig.js'); const {generatePDFFromHTML,generatePDFFromURL,generatePDFFromJSON,loadSavedJson } = require("./generatePDFHandler"); const generatePortalIntegratedTemplate = require("./portal/generatePortalIntegratedHandler.js"); const submitForPortalAction = require("./portal/savePortalFormDataHandler.js"); const loadPortalIntegratedForm = require("./portal/loadPortalIntegratedHandler.js"); - - -const getFormsFromFormTemplate = require("./formRepoHandler"); +require("./formRepoHandler"); +const {getProcessedData} = require("./icmJsonClobHandler"); const router = express.Router(); const FORM_SERVER_URL = process.env.FORMSERVERURL; const ENDPOINT_URL = process.env.ENDPOINTURL; +const localhostOnlyMiddleware = (req, res, next) => { + const clientIP = req.ip || + req.connection.remoteAddress || + req.socket.remoteAddress || + (req.connection.socket ? req.connection.socket.remoteAddress : null); + + const actualIP = clientIP?.replace('::ffff:', '') || clientIP; + + const isLocalhost = clientIP === '127.0.0.1' || + clientIP === '::1' || + clientIP === 'localhost' || + actualIP?.startsWith('172.') || // Docker default bridge network + actualIP?.startsWith('192.168.') || // Docker custom networks + actualIP?.startsWith('10.'); // Docker swarm networks + + if (!isLocalhost) { + return res.status(403).json({ error: 'Localhost access only' }); + } + next(); +}; // Form Map const formMap = new Map(); @@ -115,8 +135,7 @@ router.post("/generate", generateTemplate); router.get("/getAllForms", async (request, response) => { try { - const grant = - await keycloakForFormRepo.grantManager.obtainFromClientCredentials(); + const grant = await keycloakForFormRepo.grantManager.obtainFromClientCredentials(); let endpointUrl = `http://localhost:3030/api/forms-list`; const forms = await axios.get(endpointUrl, { @@ -134,10 +153,41 @@ router.get("/getAllForms", async (request, response) => { }); +router.get("/processIcmJsonClob", localhostOnlyMiddleware, async (req, res) => { + try { + const apiHost = req.get("X-API-Host"); + const result = await getProcessedData(req.query.attachmentId, apiHost, false); + + if (result.success) { + res.status(200).json(result.data); + } else { + res.status(500).json({ error: "Error." }); + } + } catch (error) { + console.error("Error:", error.message); + res.status(500).json({ error: "Internal server error", message: error.message }); + } +}); + +router.get("/processIcmJsonClobAnswers", localhostOnlyMiddleware, async (req, res) => { + try { + const apiHost = req.get("X-API-Host"); + const result = await getProcessedData(req.query.attachmentId, apiHost, true); + + if (result.success) { + res.status(200).json(result.data); + } else { + res.status(500).json({ error: "Error." }); + } + } catch (error) { + console.error("Error:", error.message); + res.status(500).json({ error: "Internal server error", message: error.message }); + } +}); + // clear the locked by flags in ICM for the form, used when form is closed router.post("/clearICMLockedFlag", clearICMLockedFlag); - router.post("/generatePDFFromJson", generatePDFFromJSON); // Generate route @@ -149,4 +199,5 @@ router.post("/generateNewTemplate", generateNewTemplate); router.use('/pdfRender', renderRouter); router.use('/submitForPortalAction', submitForPortalAction); router.post("/loadPortalForm", loadPortalIntegratedForm); + module.exports = router; \ No newline at end of file diff --git a/saveICMdataHandler.js b/saveICMdataHandler.js index 5c508ac..9834e0e 100644 --- a/saveICMdataHandler.js +++ b/saveICMdataHandler.js @@ -133,8 +133,30 @@ async function saveICMdata(req, res) { saveJson["DocFileExt"] = "json"; saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson - let builder = new xml2js.Builder(); - saveJson["XML Hierarchy"] = builder.buildObject(saveData); + const truncatedKeysSaveData = {}; + for(let oldKey in saveData) { //This begins trunicating the JSON keys for XML (UUID should be first 8 characters) + const stringLength = oldKey.length; + const newKey = oldKey.substring(0, stringLength-28); + if (Array.isArray(saveData[oldKey]) > 0 && Object.keys(saveData[oldKey]).length > 0) { //This trunicates child/dependant objects + const childrenArray = []; + for(let i = 0; i < saveData[oldKey].length; i++) { + const truncatedChildrenKeys = {}; + for (let oldChildKey in saveData[oldKey][i]) { + const childStringLength = oldChildKey.length; + const newChildKey = oldChildKey.substring(stringLength+3, childStringLength-28); + truncatedChildrenKeys[newChildKey] = saveData[oldKey][i][oldChildKey]; + } + childrenArray.push(truncatedChildrenKeys); + } + const wrapperKey = {} + wrapperKey[newKey] = childrenArray; + truncatedKeysSaveData[`${newKey}-List`] = wrapperKey // Add a wrapper around the children/dependecies + } else { + truncatedKeysSaveData[newKey] = saveData[oldKey]; //Data is added to new JSON with the truncated key + } + } + let builder = new xml2js.Builder({xmldec: { version: '1.0' }}); + saveJson["XML Hierarchy"] = builder.buildObject(truncatedKeysSaveData); //let url = buildUrlWithParams('SIEBEL_ICM_API_HOST', 'fwd/v1.0/data/DT Form Instance Thin/DT Form Instance Thin/' + attachment_id + '/', ''); let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params); try {