refactor(api): migrate batch 6 endpoints to relayGet

This commit is contained in:
2026-03-24 09:28:01 +00:00
parent 5797443edc
commit 6da6fdb4d2
8 changed files with 144 additions and 139 deletions
+32
View File
@@ -870,3 +870,35 @@ Validation:
Follow-ups:
- Continue P2-S2 with next bounded batch (e.g. search/listing cluster) as a separate commit if required.
---
### CL-023: TASK22229 P2-S2 Batch 6 (profile/form/document-history/portal-module relay GET cluster)
date: 2026-03-24
author: Cline
scope: `pages/api/endpoint/{getpersonalaccount_api,getformdata_api,getsearchdocumenthistory_api,getsearchdocumenthistorypaged_api,getportalmoduledetails_api}.js`, `tests/phase21/endpoint-handler-contract.test.cjs`
type: change
rationale: Execute requested Batch 6 by migrating the next bounded set of read-only endpoint handlers to shared `relayGet` while preserving existing request validation and response/error contracts.
impact: Reduced endpoint relay boilerplate and improved consistency; no intended behavior contract changes.
status: completed
Summary:
- Migrated Batch 6 endpoints to shared `relayGet`:
- `getpersonalaccount_api.js`
- `getformdata_api.js`
- `getsearchdocumenthistory_api.js` (uses `requestOptionsBuilder` with `azureHeadersPaged`)
- `getsearchdocumenthistorypaged_api.js` (uses `requestOptionsBuilder` with `azureHeadersPaged`)
- `getportalmoduledetails_api.js`
- Preserved all existing required-query validation guards and existing error codes/messages.
- Updated Phase 21 endpoint contract tests to inject `relayGet` mocks for migrated handlers (validation path, catch path, and success path where 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:
- Optional next bounded batch: migrate remaining legacy relay GET handlers in advanced/basic search-document/detail clusters that still use direct axios relay patterns.
+12 -28
View File
@@ -1,13 +1,5 @@
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 whichForm = req.query.whichForm;
@@ -20,26 +12,18 @@ export default async function ApiProxy(req, res) {
});
}
try {
const token = await getToken();
const queryUrl =
"systemforms?$select=formid,name,formxml,type,objecttypecode&$filter=(objecttypecode eq 'pinswg_" +
whichForm +
"' and type eq 2)&$count=true&$top=201";
const queryUrl =
"systemforms?$select=formid,name,formxml,type,objecttypecode&$filter=(objecttypecode eq 'pinswg_" +
whichForm +
"' and type eq 2)&$count=true&$top=201";
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: "FORM_DATA_FETCH_FAILED",
message: "Failed to fetch form data"
});
}
}
});
}
+12 -26
View File
@@ -17,16 +17,8 @@
* 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 { 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 contactid = req.query.contactid;
@@ -39,24 +31,18 @@ export default async function ApiProxy(req, res) {
});
}
try {
const token = await getToken();
const queryUrl =
"contacts(" +
contactid +
")?$select=firstname, lastname, emailaddress1, telephone1, company, address1_line1,address1_line2,address1_city, address1_county,address1_postalcode,pinswg_typeofinvolvement,pinswg_contact_associatedlpa,pinswg_preferredlanguage&$count=true";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
);
const queryUrl =
"contacts(" +
contactid +
")?$select=firstname, lastname, emailaddress1, telephone1, company, address1_line1,address1_line2,address1_city, address1_county,address1_postalcode,pinswg_typeofinvolvement,pinswg_contact_associatedlpa,pinswg_preferredlanguage&$count=true";
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
return relayGet({
queryUrl,
res,
errorResponse: {
status: 400,
code: "PERSONAL_ACCOUNT_FETCH_FAILED",
message: "Failed to fetch personal account"
});
}
}
});
}
@@ -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_FETCH_FAILED",
message: "Failed to fetch portal module details"
});
}
}
});
}
@@ -16,16 +16,9 @@
* description: Success
*/
import axios from "axios";
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";
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 documentID = req.query.documentid;
@@ -38,24 +31,18 @@ export default async function ApiProxy(req, res) {
});
}
try {
const token = await getToken();
const queryUrl =
"pinswg_documenthistories?$count=true&$select=_pinswg_documentid_value,pinswg_createddate,pinswg_state,pinswg_fileexists,statecode,pinswg_publisheddate&$filter=_pinswg_documentid_value eq " +
documentID;
const queryUrl =
"pinswg_documenthistories?$count=true&$select=_pinswg_documentid_value,pinswg_createddate,pinswg_state,pinswg_fileexists,statecode,pinswg_publisheddate&$filter=_pinswg_documentid_value eq " +
documentID;
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
return relayGet({
queryUrl,
res,
requestOptionsBuilder: (accessToken) => azureHeadersPaged(accessToken),
errorResponse: {
status: 400,
code: "SEARCH_DOCUMENT_HISTORY_FETCH_FAILED",
message: "Failed to fetch search document history"
});
}
}
});
}
@@ -42,16 +42,9 @@
* description: Success
*/
import axios from "axios";
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";
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 documentID = req.query.documentid;
@@ -64,29 +57,22 @@ export default async function ApiProxy(req, res) {
});
}
try {
const token = await getToken();
const queryUrl =
"pinswg_documenthistories?$count=true&$select=_pinswg_documentid_value,pinswg_createddate,pinswg_state,pinswg_fileexists,statecode,pinswg_publisheddate&$filter=_pinswg_documentid_value eq " +
documentID;
// +
// (typeof pageNumber != "undefined"
// ? "&$skiptoken=" + ('<cookie pagenumber="' + pageNumber + '" />')
// : "");
const queryUrl =
"pinswg_documenthistories?$count=true&$select=_pinswg_documentid_value,pinswg_createddate,pinswg_state,pinswg_fileexists,statecode,pinswg_publisheddate&$filter=_pinswg_documentid_value eq " +
documentID;
// +
// (typeof pageNumber != "undefined"
// ? "&$skiptoken=" + ('<cookie pagenumber="' + pageNumber + '" />')
// : "");
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
return relayGet({
queryUrl,
res,
requestOptionsBuilder: (accessToken) => azureHeadersPaged(accessToken),
errorResponse: {
status: 400,
code: "SEARCH_DOCUMENT_HISTORY_PAGED_FETCH_FAILED",
message: "Failed to fetch paged search document history"
});
}
}
});
}
+3
View File
@@ -41,6 +41,8 @@ const loadModule = (relativePath, injected = {}) => {
source +=
'\nif (typeof ApiProxy !== "undefined" && !module.exports.default) module.exports.default = ApiProxy;\n';
const defaultRelayGet = async () => ({});
const context = {
module: { exports: {} },
exports: {},
@@ -52,6 +54,7 @@ const loadModule = (relativePath, injected = {}) => {
warn: () => {},
error: () => {}
},
relayGet: defaultRelayGet,
...injected
};
@@ -947,6 +947,7 @@ test("getbasicsearch returns SEARCH_STRING_REQUIRED when searchString missing",
const mod = loadModule("pages/api/endpoint/getbasicsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -966,6 +967,8 @@ test("getbasicsearch catch path returns BASIC_SEARCH_FETCH_FAILED", async () =>
const mod = loadModule("pages/api/endpoint/getbasicsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -992,6 +995,7 @@ test("getcasemessage returns CASE_ID_REQUIRED when id missing", async () => {
const mod = loadModule("pages/api/endpoint/getcasemessage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -1011,6 +1015,8 @@ test("getcasemessage catch path returns CASE_MESSAGE_FETCH_FAILED", async () =>
const mod = loadModule("pages/api/endpoint/getcasemessage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -1195,6 +1201,7 @@ test("getsearchdocumenthistory returns DOCUMENT_ID_REQUIRED when documentid miss
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -1217,6 +1224,8 @@ test("getsearchdocumenthistory catch path returns SEARCH_DOCUMENT_HISTORY_FETCH_
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -1246,6 +1255,10 @@ test("getsearchdocumenthistory success returns existing data payload contract",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res }) =>
respondSuccessMock(res, {
value: [{ _pinswg_documentid_value: "d1" }]
}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -1274,6 +1287,7 @@ test("getsearchdocumenthistorypaged returns DOCUMENT_ID_REQUIRED when documentid
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -1296,6 +1310,8 @@ test("getsearchdocumenthistorypaged catch path returns SEARCH_DOCUMENT_HISTORY_P
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -2065,6 +2081,8 @@ test("getbasicdnssearch catch path returns BASIC_DNS_SEARCH_FETCH_FAILED", async
const mod = loadModule("pages/api/endpoint/getbasicdnssearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -2259,6 +2277,7 @@ test("getportalmoduledetails returns APPEAL_TYPE_REQUIRED when appealType missin
const mod = loadModule("pages/api/endpoint/getportalmoduledetails_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
getSelectQuery: () => "&$select=pinswg_name",
hashAPIPath: () => "&hash=expected",
@@ -2279,6 +2298,8 @@ test("getportalmoduledetails catch path returns PORTAL_MODULE_DETAILS_FETCH_FAIL
const mod = loadModule("pages/api/endpoint/getportalmoduledetails_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
getSelectQuery: () => "&$select=pinswg_name",
hashAPIPath: () => "&hash=expected",
@@ -2853,6 +2874,8 @@ test("getlpa catch path returns LPA_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getlpa_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -2932,6 +2955,8 @@ test("getprojecttypes catch path returns PROJECT_TYPES_FETCH_FAILED", async () =
const mod = loadModule("pages/api/endpoint/getprojecttypes_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -2958,6 +2983,8 @@ test("getappealtypes catch path returns APPEAL_TYPES_FETCH_FAILED", async () =>
const mod = loadModule("pages/api/endpoint/getappealtypes_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -2985,6 +3012,7 @@ test("getlinkedcases returns PARENT_INCIDENT_ID_REQUIRED when parentincidentid m
const mod = loadModule("pages/api/endpoint/getlinkedcases_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -3007,6 +3035,8 @@ test("getlinkedcases catch path returns LINKED_CASES_FETCH_FAILED", async () =>
const mod = loadModule("pages/api/endpoint/getlinkedcases_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -3033,6 +3063,7 @@ test("getformdata returns WHICH_FORM_REQUIRED when whichForm missing", async ()
const mod = loadModule("pages/api/endpoint/getformdata_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -3052,6 +3083,8 @@ test("getformdata catch path returns FORM_DATA_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getformdata_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -3075,6 +3108,7 @@ test("getispublishedbyid returns SEARCH_STRING_REQUIRED when searchString missin
const mod = loadModule("pages/api/endpoint/getispublishedbyid_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -3094,6 +3128,8 @@ test("getispublishedbyid catch path returns IS_PUBLISHED_FETCH_FAILED", async ()
const mod = loadModule("pages/api/endpoint/getispublishedbyid_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
@@ -3257,6 +3293,7 @@ test("getbasicdnsurlsearch returns SEARCH_STRING_REQUIRED when searchString miss
const mod = loadModule("pages/api/endpoint/getbasicdnsurlsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -3277,6 +3314,8 @@ test("getbasicdnsurlsearch catch path returns BASIC_DNS_URL_SEARCH_FETCH_FAILED"
const mod = loadModule("pages/api/endpoint/getbasicdnsurlsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -3304,6 +3343,7 @@ test("getpartsavedappeal returns SEARCH_STRING_REQUIRED when searchString missin
const mod = loadModule("pages/api/endpoint/getpartsavedappeal_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async () => ({}),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
@@ -3324,6 +3364,8 @@ test("getpartsavedappeal catch path returns PART_SAVED_APPEAL_FETCH_FAILED", asy
const mod = loadModule("pages/api/endpoint/getpartsavedappeal_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
relayGet: async ({ res, errorResponse }) =>
respondErrorMock(res, errorResponse),
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),