diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md
index c564b26d..6556676e 100644
--- a/memory-bank/change-log.md
+++ b/memory-bank/change-log.md
@@ -974,3 +974,35 @@ Validation:
Follow-ups:
- Continue P2-S2 with next bounded batch from remaining legacy GET endpoints (currently: `createwatchedcases_api`, `deletewatchedcasesproxy_api`, `getadvancedsearch_api`, `getadvancedsearchpaged_api`, `getappealid_api`, `getbasicdnssearchpaged_api`, `getbasicpartsaveddetails_api`, `getbasicsearch_by_address_api`, `getbasicsearch_by_lparref_api`, `getdnscoords_api`, `getdnslist_api`, `getmylpacases_api`, `getportallogin_api`).
+
+---
+
+### CL-026: TASK22229 P2-S2 Batch 9 (appeal-id + DNS list/paged + part-saved relay GET cluster)
+
+date: 2026-03-24
+author: Cline
+scope: `pages/api/endpoint/{getappealid_api,getbasicdnssearchpaged_api,getbasicpartsaveddetails_api,getdnslist_api}.js`, `tests/phase21/endpoint-handler-contract.test.cjs`
+type: change
+rationale: Continue bounded P2-S2 relay migration by moving another coherent set of read-only handlers to shared `relayGet`, preserving existing guards, transforms, and error contracts.
+impact: Reduced duplicated relay plumbing and improved consistency for DNS list/paged and appeal detail lookup endpoints; no intended API contract changes.
+status: completed
+
+Summary:
+
+- Migrated Batch 9 endpoints to shared `relayGet`:
+ - `getappealid_api.js`
+ - `getbasicdnssearchpaged_api.js`
+ - `getbasicpartsaveddetails_api.js`
+ - `getdnslist_api.js`
+- Preserved existing input validation guards and endpoint-specific error contracts.
+- Preserved endpoint-specific transform behavior (`@odata.nextLink` normalization in DNS list/paged handlers).
+- Updated phase21 endpoint contract tests to inject `relayGet` mocks for these migrated handlers in guard and catch-path assertions.
+
+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 P2-S2 with next bounded batch from remaining legacy relay GET endpoints (`createwatchedcases_api`, `deletewatchedcasesproxy_api`, `getadvancedsearch_api`, `getadvancedsearchpaged_api`, `getbasicsearch_by_address_api`, `getbasicsearch_by_lparref_api`, `getdnscoords_api`, `getmylpacases_api`, `getportallogin_api`).
diff --git a/pages/api/endpoint/getappealid_api.js b/pages/api/endpoint/getappealid_api.js
index d01ce523..952f82d6 100644
--- a/pages/api/endpoint/getappealid_api.js
+++ b/pages/api/endpoint/getappealid_api.js
@@ -28,16 +28,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";
export default async function ApiProxy(req, res) {
const caseReference = req.query.caseReference;
@@ -77,30 +70,24 @@ export default async function ApiProxy(req, res) {
});
}
- try {
- const token = await getToken();
- const escapedCaseReference = caseReference.split("'").join("''");
+ const escapedCaseReference = caseReference.split("'").join("''");
- const queryUrl =
- updateFormCollection +
- "?$count=true&$select=_" +
- primaryAttribute +
- "s_value&$filter=pinswg_name eq '" +
- escapedCaseReference +
- "'";
+ const queryUrl =
+ updateFormCollection +
+ "?$count=true&$select=_" +
+ primaryAttribute +
+ "s_value&$filter=pinswg_name eq '" +
+ escapedCaseReference +
+ "'";
- 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,
+ requestOptionsBuilder: (accessToken) => azureHeaders(accessToken),
+ errorResponse: {
status: 400,
code: "APPEAL_ID_FETCH_FAILED",
message: "Failed to fetch appeal id"
- });
- }
+ }
+ });
}
diff --git a/pages/api/endpoint/getbasicdnssearchpaged_api.js b/pages/api/endpoint/getbasicdnssearchpaged_api.js
index 4d8d7791..76336faa 100644
--- a/pages/api/endpoint/getbasicdnssearchpaged_api.js
+++ b/pages/api/endpoint/getbasicdnssearchpaged_api.js
@@ -42,17 +42,10 @@
* description: Success
*/
-import axios from "axios";
import _ from "lodash";
import { azureHeadersPagedCustom } from "../../../actions/core/headers";
-import { getToken } from "../../../actions/core/token";
-import { consoleLogger } from "../../../actions/core/logger";
-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 pageNumber = req.query.pageNumber;
@@ -87,36 +80,33 @@ export default async function ApiProxy(req, res) {
});
}
- try {
- const token = await getToken();
+ const queryUrl =
+ "incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference&$expand=primarycontactid($select=fullname)&$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=" +
+ orderby +
+ " " +
+ fieldSort +
+ "&$count=true" +
+ (typeof pageNumber != "undefined"
+ ? "&$skiptoken=" + ''
+ : "");
- const queryUrl =
- "incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference&$expand=primarycontactid($select=fullname)&$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=" +
- orderby +
- " " +
- fieldSort +
- "&$count=true" +
- (typeof pageNumber != "undefined"
- ? "&$skiptoken=" + ''
- : "");
+ return relayGet({
+ queryUrl,
+ res,
+ requestOptionsBuilder: (accessToken) =>
+ azureHeadersPagedCustom(accessToken, showNumberOfRecords),
+ transformData: (data) => {
+ if (_.has(data, "@odata.nextLink") === true) {
+ const dataStr = JSON.stringify(data["@odata.nextLink"]);
+ data["@odata.nextLink"] = dataStr.split("/v8.2/")[1];
+ }
- const { data } = await axios.get(
- WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
- azureHeadersPagedCustom(token.access_token, showNumberOfRecords)
- );
-
- 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: "BASIC_DNS_SEARCH_PAGED_FETCH_FAILED",
message: "Failed to fetch paged basic DNS search results"
- });
- }
+ }
+ });
}
diff --git a/pages/api/endpoint/getbasicpartsaveddetails_api.js b/pages/api/endpoint/getbasicpartsaveddetails_api.js
index 47ced874..a6fe1011 100644
--- a/pages/api/endpoint/getbasicpartsaveddetails_api.js
+++ b/pages/api/endpoint/getbasicpartsaveddetails_api.js
@@ -1,13 +1,6 @@
-import axios from "axios";
import { azureHeadersNoOdata } 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";
export default async function ApiProxy(req, res) {
const appealTypeName = req.query.appealTypeName;
@@ -44,29 +37,23 @@ export default async function ApiProxy(req, res) {
});
}
- try {
- const token = await getToken();
+ const queryUrl =
+ appealTypeName +
+ "?$filter=_" +
+ primaryIdAttribute +
+ "s_value eq " +
+ incidentID +
+ "&$count=true";
- const queryUrl =
- appealTypeName +
- "?$filter=_" +
- primaryIdAttribute +
- "s_value eq " +
- incidentID +
- "&$count=true";
-
- const { data } = await axios.get(
- WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
- azureHeadersNoOdata(token.access_token)
- );
-
- return respondSuccess(res, data);
- } catch (error) {
- consoleLogger(error);
- return respondError(res, {
+ return relayGet({
+ queryUrl,
+ res,
+ requestOptionsBuilder: (accessToken) =>
+ azureHeadersNoOdata(accessToken),
+ errorResponse: {
status: 400,
code: "BASIC_PART_SAVED_DETAILS_FETCH_FAILED",
message: "Failed to fetch basic part-saved details"
- });
- }
+ }
+ });
}
diff --git a/pages/api/endpoint/getdnslist_api.js b/pages/api/endpoint/getdnslist_api.js
index 14251a39..ab1439f3 100644
--- a/pages/api/endpoint/getdnslist_api.js
+++ b/pages/api/endpoint/getdnslist_api.js
@@ -1,40 +1,29 @@
-import axios from "axios";
import _ from "lodash";
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";
export default async function ApiProxy(req, res) {
- try {
- const token = await getToken();
+ const queryUrl =
+ "incidents?$select=pinswg_environmentalstatementlocation,pinswg_appealcasetype,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,statuscode,ticketnumber,title &$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true";
- const queryUrl =
- "incidents?$select=pinswg_environmentalstatementlocation,pinswg_appealcasetype,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,statuscode,ticketnumber,title &$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true";
+ return relayGet({
+ queryUrl,
+ res,
+ requestOptionsBuilder: (accessToken) => azureHeaders(accessToken),
+ transformData: (data) => {
+ let dataStr;
- const { data } = await axios.get(
- WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
- azureHeaders(token.access_token)
- );
+ _.has(data, "@odata.nextLink") === true &&
+ ((dataStr = JSON.stringify(data["@odata.nextLink"])),
+ (data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]));
- let dataStr;
-
- _.has(data, "@odata.nextLink") === true &&
- ((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: "DNS_LIST_FETCH_FAILED",
message: "Failed to fetch DNS list"
- });
- }
+ }
+ });
}
diff --git a/tests/phase21/endpoint-handler-contract.test.cjs b/tests/phase21/endpoint-handler-contract.test.cjs
index e4f9eae9..f0d311c0 100644
--- a/tests/phase21/endpoint-handler-contract.test.cjs
+++ b/tests/phase21/endpoint-handler-contract.test.cjs
@@ -2141,11 +2141,8 @@ test("getbasicdnssearchpaged returns ORDER_BY_REQUIRED when orderby missing", as
const mod = loadModule("pages/api/endpoint/getbasicdnssearchpaged_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
+ relayGet: async () => ({}),
azureHeadersPagedCustom: () => ({}),
- axios: { get: async () => ({ data: { value: [] } }) },
- consoleLogger: () => {},
_: { has: () => false }
});
@@ -2161,15 +2158,9 @@ test("getbasicdnssearchpaged catch path returns BASIC_DNS_SEARCH_PAGED_FETCH_FAI
const mod = loadModule("pages/api/endpoint/getbasicdnssearchpaged_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
+ relayGet: async ({ res, errorResponse }) =>
+ respondErrorMock(res, errorResponse),
azureHeadersPagedCustom: () => ({}),
- axios: {
- get: async () => {
- throw new Error("relay failed");
- }
- },
- consoleLogger: () => {},
_: { has: () => false }
});
@@ -2939,11 +2930,8 @@ test("getappealid returns CASE_REFERENCE_REQUIRED when caseReference missing", a
const mod = loadModule("pages/api/endpoint/getappealid_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
- azureHeaders: () => ({}),
- axios: { get: async () => ({ data: { value: [] } }) },
- consoleLogger: () => {}
+ relayGet: async () => ({}),
+ azureHeaders: () => ({})
});
const req = {
@@ -2966,15 +2954,9 @@ test("getappealid catch path returns APPEAL_ID_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getappealid_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
- azureHeaders: () => ({}),
- axios: {
- get: async () => {
- throw new Error("relay failed");
- }
- },
- consoleLogger: () => {}
+ relayGet: async ({ res, errorResponse }) =>
+ respondErrorMock(res, errorResponse),
+ azureHeaders: () => ({})
});
const req = {
@@ -3196,15 +3178,9 @@ test("getdnslist catch path returns DNS_LIST_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getdnslist_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
+ relayGet: async ({ res, errorResponse }) =>
+ respondErrorMock(res, errorResponse),
azureHeaders: () => ({}),
- axios: {
- get: async () => {
- throw new Error("relay failed");
- }
- },
- consoleLogger: () => {},
_: { has: () => false }
});
@@ -3270,11 +3246,8 @@ test("getbasicpartsaveddetails returns APPEAL_TYPE_NAME_REQUIRED when appealType
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
- azureHeadersNoOdata: () => ({}),
- axios: { get: async () => ({ data: { value: [] } }) },
- consoleLogger: () => {}
+ relayGet: async () => ({}),
+ azureHeadersNoOdata: () => ({})
}
);
@@ -3300,15 +3273,9 @@ test("getbasicpartsaveddetails catch path returns BASIC_PART_SAVED_DETAILS_FETCH
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
- getToken: async () => ({ access_token: "token" }),
- hashAPIPath: () => "&hash=expected",
- azureHeadersNoOdata: () => ({}),
- axios: {
- get: async () => {
- throw new Error("relay failed");
- }
- },
- consoleLogger: () => {}
+ relayGet: async ({ res, errorResponse }) =>
+ respondErrorMock(res, errorResponse),
+ azureHeadersNoOdata: () => ({})
}
);