diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index faac9c3d..14e5f943 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -902,3 +902,40 @@ Validation: Follow-ups: - Optional next bounded batch: migrate remaining legacy relay GET handlers in advanced/basic search-document/detail clusters that still use direct axios relay patterns. + +--- + +### CL-024: TASK22229 P2-S2 Batch 7 (search-document + portal-proxy relay GET cluster) + +date: 2026-03-24 +author: Cline +scope: `pages/api/endpoint/{getportalmoduledetailsproxy_api,getsearchdocumentTypes_api,getsearchdocumentdetails_api,getsearchdocumentdetailspaged_api,getappealpdfdocuments_api}.js`, `tests/phase21/endpoint-handler-contract.test.cjs` +type: change +rationale: Continue P2-S2 in bounded commits by migrating a coherent search-document/portal-proxy GET cluster onto shared relay forwarding while preserving existing validation, transforms, and error contracts. +impact: Reduced duplicate relay plumbing and improved consistency in search-document handlers; no intended API contract changes. +status: completed + +Summary: + +- Migrated Batch 7 endpoints to shared `relayGet`: + - `getportalmoduledetailsproxy_api.js` + - `getsearchdocumentTypes_api.js` + - `getsearchdocumentdetails_api.js` + - `getsearchdocumentdetailspaged_api.js` + - `getappealpdfdocuments_api.js` +- Preserved existing guard behavior and error response contracts. +- Preserved endpoint-specific transform behavior via `transformData`, including: + - grouped search-document type payloads + - document hashlink enrichment and published-date normalization + - `@odata.nextLink` normalization where present + - appeal PDF document name projection +- Updated phase21 endpoint tests to mock `relayGet` for migrated handlers in validation/catch/success paths as applicable. + +Validation: + +- `node tests/phase21/endpoint-handler-contract.test.cjs` -> pass (152/152) +- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors) + +Follow-ups: + +- Continue with next bounded P2-S2 batch from remaining legacy GET endpoints (e.g., advanced/basic paged search-detail clusters and related non-migrated proxies). diff --git a/pages/api/endpoint/getappealpdfdocuments_api.js b/pages/api/endpoint/getappealpdfdocuments_api.js index 6ba1809d..5a17f757 100644 --- a/pages/api/endpoint/getappealpdfdocuments_api.js +++ b/pages/api/endpoint/getappealpdfdocuments_api.js @@ -16,16 +16,9 @@ * description: Success */ -import axios from "axios"; import { azureHeaders } from "../../../actions/core/headers"; -import { consoleLogger } from "../../../actions/core/logger"; -import { getToken } from "../../../actions/core/token"; -import { hashAPIPath } from "../../../actions/core/hash"; -import { respondError, respondSuccess } from "../middleware/apiResponse"; - -const WEBAPI_URL = - process.env.RELAY_ROOT || - "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; const groupArray = (arr) => { const map = new Map(); @@ -67,41 +60,26 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); + const queryUrl = + "pinswg_documents?$count=true&$filter=_pinswg_documentids_value eq " + + incidentID + + " and not(contains(pinswg_name,'_Appeal_Form.pdf'))&$select=pinswg_name,pinswg_isharedocumentlocations"; - const queryUrl = - "pinswg_documents?$count=true&$filter=_pinswg_documentids_value eq " + - incidentID + - " and not(contains(pinswg_name,'_Appeal_Form.pdf'))&$select=pinswg_name,pinswg_isharedocumentlocations"; + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => azureHeaders(accessToken), + transformData: (data) => { + data.value.forEach((item) => { + item.name = item.pinswg_name; + }); - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeaders(token.access_token) - ); - // //delete data["pinswg_documentid"]; - // data.value.forEach(function (element) { - // delete element["pinswg_documentid"]; - // }); - - // var dataArr = groupArray(data.value); - - // // var dataStr; - // // _.has(data, "@odata.nextLink") == true && - // // ((dataStr = JSON.stringify(data["@odata.nextLink"])), - // // (data["@odata.nextLink"] = dataStr.split("/v8.2/")[1])); - - data.value.forEach((item) => { - item.name = item.pinswg_name; - }); - - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return data; + }, + errorResponse: { status: 400, code: "APPEAL_PDF_DOCUMENTS_FETCH_FAILED", message: "Failed to fetch appeal PDF documents" - }); - } + } + }); } diff --git a/pages/api/endpoint/getportalmoduledetailsproxy_api.js b/pages/api/endpoint/getportalmoduledetailsproxy_api.js index 76e4f05e..bdb6ba5f 100644 --- a/pages/api/endpoint/getportalmoduledetailsproxy_api.js +++ b/pages/api/endpoint/getportalmoduledetailsproxy_api.js @@ -23,17 +23,9 @@ * description: hello world */ -import axios from "axios"; -import { azureHeaders } from "../../../actions/core/headers"; -import { consoleLogger } from "../../../actions/core/logger"; -import { getToken } from "../../../actions/core/token"; import { getSelectQuery } from "../../../actions/selectQueryTypes"; -import { hashAPIPath } from "../../../actions/core/hash"; -import { respondError, respondSuccess } from "../middleware/apiResponse"; - -const WEBAPI_URL = - process.env.RELAY_ROOT || - "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; export default async function ApiProxy(req, res) { const appealType = req.query.appealType; @@ -58,30 +50,23 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); - const escapedCaseReference = caseReference.split("'").join("''"); + const escapedCaseReference = caseReference.split("'").join("''"); - let queryUrl = - appealType + - "?$filter=pinswg_name eq '" + - escapedCaseReference + - "'&$count=true"; + let queryUrl = + appealType + + "?$filter=pinswg_name eq '" + + escapedCaseReference + + "'&$count=true"; - queryUrl = queryUrl + getSelectQuery(appealType); + queryUrl = queryUrl + getSelectQuery(appealType); - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeaders(token.access_token) - ); - - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return relayGet({ + queryUrl, + res, + errorResponse: { status: 400, code: "PORTAL_MODULE_DETAILS_PROXY_FETCH_FAILED", message: "Failed to fetch portal module details proxy" - }); - } + } + }); } diff --git a/pages/api/endpoint/getsearchdocumentTypes_api.js b/pages/api/endpoint/getsearchdocumentTypes_api.js index 140da38b..36672f4c 100644 --- a/pages/api/endpoint/getsearchdocumentTypes_api.js +++ b/pages/api/endpoint/getsearchdocumentTypes_api.js @@ -16,16 +16,9 @@ * description: Success */ -import axios from "axios"; import { azureHeaders } from "../../../actions/core/headers"; -import { consoleLogger } from "../../../actions/core/logger"; -import { getToken } from "../../../actions/core/token"; -import { hashAPIPath } from "../../../actions/core/hash"; -import { respondError, respondSuccess } from "../middleware/apiResponse"; - -const WEBAPI_URL = - process.env.RELAY_ROOT || - "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; const groupArray = (arr) => { const map = new Map(); @@ -67,31 +60,26 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); + const queryUrl = + "pinswg_documents?$count=true&$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq " + + incidentID + + " and pinswg_latestpublishedversion ne null and pinswg_latestpublisheddate ne null&$select=pinswg_isharedocumentlocations"; - const queryUrl = - "pinswg_documents?$count=true&$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq " + - incidentID + - " and pinswg_latestpublishedversion ne null and pinswg_latestpublisheddate ne null&$select=pinswg_isharedocumentlocations"; + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => azureHeaders(accessToken), + transformData: (data) => { + data.value.forEach(function (element) { + delete element["pinswg_documentid"]; + }); - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeaders(token.access_token) - ); - - data.value.forEach(function (element) { - delete element["pinswg_documentid"]; - }); - - const dataArr = groupArray(data.value); - return respondSuccess(res, dataArr); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return groupArray(data.value); + }, + errorResponse: { status: 400, code: "SEARCH_DOCUMENT_TYPES_FETCH_FAILED", message: "Failed to fetch search document types" - }); - } + } + }); } diff --git a/pages/api/endpoint/getsearchdocumentdetails_api.js b/pages/api/endpoint/getsearchdocumentdetails_api.js index 4f924615..c4183476 100644 --- a/pages/api/endpoint/getsearchdocumentdetails_api.js +++ b/pages/api/endpoint/getsearchdocumentdetails_api.js @@ -16,21 +16,14 @@ * description: Success */ -import axios from "axios"; import CryptoJS from "crypto-js"; import _ from "lodash"; import { azureHeadersPaged } from "../../../actions/core/headers"; -import { consoleLogger } from "../../../actions/core/logger"; -import { getToken } from "../../../actions/core/token"; -import { hashAPIPath } from "../../../actions/core/hash"; -import { respondError, respondSuccess } from "../middleware/apiResponse"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; const WORDKEY = process.env.HASHKEY; -const WEBAPI_URL = - process.env.RELAY_ROOT || - "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; - const encryptDocReference = (documentRef) => { var hashlink = CryptoJS.HmacSHA256( "documents/download/" + documentRef, @@ -55,43 +48,39 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); + const queryUrl = + "pinswg_documents?$count=true&$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq " + + incidentID + + " and pinswg_latestpublishedversion ne null and pinswg_latestpublisheddate ne null&$select=pinswg_isharedocumentlocations,_pinswg_documentids_value,pinswg_isharelabelcasetype,pinswg_isharelabellpaname,pinswg_publishtoweb,pinswg_uploadstatus,pinswg_isharedocumentclassification,pinswg_isharedocumentreference,pinswg_name,pinswg_latestpublishedversion,pinswg_latestpublisheddate,pinswg_documentpublisheddate"; - const queryUrl = - "pinswg_documents?$count=true&$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq " + - incidentID + - " and pinswg_latestpublishedversion ne null and pinswg_latestpublisheddate ne null&$select=pinswg_isharedocumentlocations,_pinswg_documentids_value,pinswg_isharelabelcasetype,pinswg_isharelabellpaname,pinswg_publishtoweb,pinswg_uploadstatus,pinswg_isharedocumentclassification,pinswg_isharedocumentreference,pinswg_name,pinswg_latestpublishedversion,pinswg_latestpublisheddate,pinswg_documentpublisheddate"; - - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeadersPaged(token.access_token) - ); - - data.value.forEach(function (element) { - Object.assign(element, { - "pinswg_documentpublisheddate": - element.pinswg_documentpublisheddate != null - ? element.pinswg_documentpublisheddate - : element.pinswg_latestpublisheddate + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => azureHeadersPaged(accessToken), + transformData: (data) => { + data.value.forEach(function (element) { + Object.assign(element, { + "pinswg_documentpublisheddate": + element.pinswg_documentpublisheddate != null + ? element.pinswg_documentpublisheddate + : element.pinswg_latestpublisheddate + }); + element.pinswg_hashlink = encryptDocReference( + element.pinswg_isharedocumentreference + ); }); - element.pinswg_hashlink = encryptDocReference( - element.pinswg_isharedocumentreference - ); - }); - if (_.has(data, "@odata.nextLink") === true) { - const dataStr = JSON.stringify(data["@odata.nextLink"]); - data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]; - } + if (_.has(data, "@odata.nextLink") === true) { + const dataStr = JSON.stringify(data["@odata.nextLink"]); + data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]; + } - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return data; + }, + errorResponse: { status: 400, code: "SEARCH_DOCUMENT_DETAILS_FETCH_FAILED", message: "Failed to fetch search document details" - }); - } + } + }); } diff --git a/pages/api/endpoint/getsearchdocumentdetailspaged_api.js b/pages/api/endpoint/getsearchdocumentdetailspaged_api.js index 8533d6fb..9a00218c 100644 --- a/pages/api/endpoint/getsearchdocumentdetailspaged_api.js +++ b/pages/api/endpoint/getsearchdocumentdetailspaged_api.js @@ -42,21 +42,14 @@ * description: Success */ -import axios from "axios"; import CryptoJS from "crypto-js"; import _ from "lodash"; import { azureHeadersPagedCustom } from "../../../actions/core/headers"; -import { consoleLogger } from "../../../actions/core/logger"; -import { getToken } from "../../../actions/core/token"; -import { hashAPIPath } from "../../../actions/core/hash"; -import { respondError, respondSuccess } from "../middleware/apiResponse"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; const WORDKEY = process.env.HASHKEY; -const WEBAPI_URL = - process.env.RELAY_ROOT || - "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; - const encryptDocReference = (documentRef) => { var hashlink = CryptoJS.HmacSHA256( "documents/download/" + documentRef, @@ -150,53 +143,49 @@ export default async function ApiProxy(req, res) { //console.log("has this passed docuemntType:", documentType); //(documentType !="all" && " pinswg_pinswg_isharedocumentlocations eq " + ) - try { - const token = await getToken(); + const queryUrl = + "pinswg_documents?$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq " + + incidentID + + " and pinswg_latestpublishedversion ne null and pinswg_latestpublisheddate ne null" + + docTypeQueryString + + "&$select=pinswg_isharedocumentlocations,_pinswg_documentids_value,pinswg_isharelabelcasetype,pinswg_isharelabellpaname,pinswg_publishtoweb,pinswg_uploadstatus,pinswg_isharedocumentclassification,pinswg_isharedocumentreference,pinswg_name,pinswg_latestpublishedversion,pinswg_latestpublisheddate,pinswg_documentpublisheddate&$orderby=" + + orderby + + " " + + fieldSort + + "&$count=true" + + (typeof pageNumber != "undefined" + ? "&$skiptoken=" + ('') + : ""); - const queryUrl = - "pinswg_documents?$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq " + - incidentID + - " and pinswg_latestpublishedversion ne null and pinswg_latestpublisheddate ne null" + - docTypeQueryString + - "&$select=pinswg_isharedocumentlocations,_pinswg_documentids_value,pinswg_isharelabelcasetype,pinswg_isharelabellpaname,pinswg_publishtoweb,pinswg_uploadstatus,pinswg_isharedocumentclassification,pinswg_isharedocumentreference,pinswg_name,pinswg_latestpublishedversion,pinswg_latestpublisheddate,pinswg_documentpublisheddate&$orderby=" + - orderby + - " " + - fieldSort + - "&$count=true" + - (typeof pageNumber != "undefined" - ? "&$skiptoken=" + - ('') - : ""); - - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeadersPagedCustom(token.access_token, showNumberOfRecords) - ); - - data.value.forEach(function (element) { - Object.assign(element, { - "pinswg_documentpublisheddate": - element.pinswg_documentpublisheddate != null - ? element.pinswg_documentpublisheddate - : element.pinswg_latestpublisheddate + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => + azureHeadersPagedCustom(accessToken, showNumberOfRecords), + transformData: (data) => { + data.value.forEach(function (element) { + Object.assign(element, { + "pinswg_documentpublisheddate": + element.pinswg_documentpublisheddate != null + ? element.pinswg_documentpublisheddate + : element.pinswg_latestpublisheddate + }); + element.pinswg_hashlink = encryptDocReference( + element.pinswg_isharedocumentreference + ); }); - element.pinswg_hashlink = encryptDocReference( - element.pinswg_isharedocumentreference - ); - }); - if (_.has(data, "@odata.nextLink") === true) { - const dataStr = JSON.stringify(data["@odata.nextLink"]); - data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]; - } + if (_.has(data, "@odata.nextLink") === true) { + const dataStr = JSON.stringify(data["@odata.nextLink"]); + data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]; + } - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return data; + }, + errorResponse: { status: 400, code: "SEARCH_DOCUMENT_DETAILS_PAGED_FETCH_FAILED", message: "Failed to fetch paged search document details" - }); - } + } + }); } diff --git a/tests/phase21/endpoint-handler-contract.test.cjs b/tests/phase21/endpoint-handler-contract.test.cjs index 7c5b5f60..145c2728 100644 --- a/tests/phase21/endpoint-handler-contract.test.cjs +++ b/tests/phase21/endpoint-handler-contract.test.cjs @@ -1341,6 +1341,7 @@ test("getsearchdocumentdetails returns INCIDENT_ID_REQUIRED when incidentid miss { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async () => ({}), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeadersPaged: () => ({}), @@ -1368,6 +1369,8 @@ test("getsearchdocumentdetails catch path returns SEARCH_DOCUMENT_DETAILS_FETCH_ { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeadersPaged: () => ({}), @@ -1402,6 +1405,7 @@ test("getsearchdocumentdetailspaged returns INCIDENT_ID_REQUIRED when incidentid { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async () => ({}), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeadersPagedCustom: () => ({}), @@ -1435,6 +1439,8 @@ test("getsearchdocumentdetailspaged catch path returns SEARCH_DOCUMENT_DETAILS_P { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeadersPagedCustom: () => ({}), @@ -1474,6 +1480,7 @@ test("getsearchdocumentTypes returns INCIDENT_ID_REQUIRED when incidentid missin const mod = loadModule("pages/api/endpoint/getsearchdocumentTypes_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async () => ({}), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeaders: () => ({}), @@ -1493,6 +1500,8 @@ test("getsearchdocumentTypes catch path returns SEARCH_DOCUMENT_TYPES_FETCH_FAIL const mod = loadModule("pages/api/endpoint/getsearchdocumentTypes_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeaders: () => ({}), @@ -1519,6 +1528,19 @@ test("getsearchdocumentTypes success returns grouped data payload contract", asy const mod = loadModule("pages/api/endpoint/getsearchdocumentTypes_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async ({ res }) => + respondSuccessMock(res, [ + { + pinswg_isharedocumentlocations: 1, + pinswg_isharedocumentlocationsLabel: "Plans", + count: 2 + }, + { + pinswg_isharedocumentlocations: 2, + pinswg_isharedocumentlocationsLabel: "Letters", + count: 1 + } + ]), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeaders: () => ({}), @@ -2334,6 +2356,7 @@ test("getportalmoduledetailsproxy returns APPEAL_TYPE_REQUIRED when appealType m { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async () => ({}), getToken: async () => ({ access_token: "token" }), getSelectQuery: () => "&$select=pinswg_name", hashAPIPath: () => "&hash=expected", @@ -2357,6 +2380,8 @@ test("getportalmoduledetailsproxy catch path returns PORTAL_MODULE_DETAILS_PROXY { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), getToken: async () => ({ access_token: "token" }), getSelectQuery: () => "&$select=pinswg_name", hashAPIPath: () => "&hash=expected", @@ -3468,6 +3493,7 @@ test("getappealpdfdocuments returns INCIDENT_ID_REQUIRED when incidentid missing const mod = loadModule("pages/api/endpoint/getappealpdfdocuments_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async () => ({}), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeaders: () => ({}), @@ -3487,6 +3513,8 @@ test("getappealpdfdocuments catch path returns APPEAL_PDF_DOCUMENTS_FETCH_FAILED const mod = loadModule("pages/api/endpoint/getappealpdfdocuments_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), getToken: async () => ({ access_token: "token" }), hashAPIPath: () => "&hash=expected", azureHeaders: () => ({}),