From 33dcb6b863ce64136c6174d5387082f49ce09f0b Mon Sep 17 00:00:00 2001 From: Robert Bond Date: Thu, 25 Jun 2026 08:24:58 +0000 Subject: [PATCH] Merged PR 2428: Add documents API grouping facade slice Add documents API grouping facade slice Related work items: #23754 --- actions/services/searchDirectService.js | 6 +- context/api-grouping-adoption-roadmap.md | 29 +++ memory-bank/change-log.md | 72 +++++++ .../get-search-document-details-paged.js | 5 + .../documents/get-search-document-details.js | 5 + .../documents/get-search-document-types.js | 5 + .../api-grouping-facade-documents.test.cjs | 189 ++++++++++++++++++ 7 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 pages/api/documents/get-search-document-details-paged.js create mode 100644 pages/api/documents/get-search-document-details.js create mode 100644 pages/api/documents/get-search-document-types.js create mode 100644 tests/phase22/api-grouping-facade-documents.test.cjs diff --git a/actions/services/searchDirectService.js b/actions/services/searchDirectService.js index 15742157..5a3385de 100644 --- a/actions/services/searchDirectService.js +++ b/actions/services/searchDirectService.js @@ -201,7 +201,7 @@ export const getBasicSearchDetailsPaged = async ( export const getSearchDocumentDetails = (incidentID) => { return getJson( - "/api/endpoint/getsearchdocumentdetails_api?incidentid=" + incidentID + "/api/documents/get-search-document-details?incidentid=" + incidentID ).catch((error) => { consoleLogger(error); throw error; @@ -210,7 +210,7 @@ export const getSearchDocumentDetails = (incidentID) => { export const getSearchDocumentTypes = (incidentID) => { return getJson( - "/api/endpoint/getsearchdocumentTypes_api?incidentid=" + incidentID + "/api/documents/get-search-document-types?incidentid=" + incidentID ).catch((error) => { consoleLogger(error); }); @@ -225,7 +225,7 @@ export const getSearchDocumentDetailsPaged = ( documentType ) => { return getJson( - "/api/endpoint/getsearchdocumentdetailspaged_api?incidentid=" + + "/api/documents/get-search-document-details-paged?incidentid=" + incidentID + "&pageNumber=" + pageNumber + diff --git a/context/api-grouping-adoption-roadmap.md b/context/api-grouping-adoption-roadmap.md index aca1c75e..19925c20 100644 --- a/context/api-grouping-adoption-roadmap.md +++ b/context/api-grouping-adoption-roadmap.md @@ -9,6 +9,10 @@ Pilot status update (2026-06-25): - first additive grouping façade pilot implemented for watched-case APIs under `pages/api/subscriptions/` - first internal consumer (`actions/services/portalDirectService.js` watched-case methods) now targets the grouped façade routes - the active watched-case vertical slice now includes grouped adoption for both normal watched-case reads and proxy watched-case reads used by the current UI journey +- second additive grouping façade vertical slice implemented for the active published-documents metadata journey under `pages/api/documents/` +- active published-document metadata consumer (`actions/services/searchDirectService.js`) now targets grouped documents façade routes for details, paged details, and document-type reads +- published document download remains on the existing grouped runtime path `pages/api/documents/download/[id].js` +- legacy document metadata endpoint routes remain canonical and stable behind the additive façade wrappers - legacy watched-case endpoint routes remain canonical and stable - no old route removal or migration has occurred - runtime behaviour is intended to remain unchanged because the grouped routes delegate to the existing handlers @@ -189,6 +193,31 @@ It means: - runtime behaviour remains unchanged through delegation - `deletewatchedcasesproxy_api` remains a legacy/support route outside the grouped pilot scope because it is not part of the currently adopted normal watched-case service path. +#### Additional vertical-slice proof point — published documents + +- The active published-documents journey now has a second complete additive grouping slice for metadata discovery only: + - grouped façade routes exist for the active metadata/type reads under `pages/api/documents/` + - `actions/services/searchDirectService.js` targets those grouped routes for: + - `getSearchDocumentDetails` + - `getSearchDocumentDetailsPaged` + - `getSearchDocumentTypes` + - legacy endpoint handlers remain present and canonical behind the façade: + - `pages/api/endpoint/getsearchdocumentdetails_api.js` + - `pages/api/endpoint/getsearchdocumentdetailspaged_api.js` + - `pages/api/endpoint/getsearchdocumentTypes_api.js` + - runtime behaviour remains unchanged through direct delegation and unchanged service-layer query contracts +- The active bounded audit also established that: + - the current UI document journey is routed through `components/case/documents.js` + - the current common metadata service layer is `actions/services/searchDirectService.js` + - the existing grouped route `pages/api/documents/download/[id].js` remains the runtime download path and was intentionally left unchanged + - `getsearchdocumenthistory_api.js` and `getsearchdocumenthistorypaged_api.js` remain present as legacy/support routes outside the active grouped adoption scope because they were not surfaced in the currently active UI journey +- For this documents slice, a complete vertical grouping slice means: + - active document metadata/type journey calls have grouped façade routes + - the active common service layer uses those grouped routes + - legacy handlers remain present and canonical + - download behaviour remains unchanged + - no route migration, deletion, or contract change has occurred + --- ### Stage 3 — Documentation Maturity diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index 3cfd5dd1..72d9c6fe 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -18,6 +18,78 @@ Follow-ups: --- +### CL-2026-06-25-API-GROUPING-FACADE-DOCUMENTS-VERTICAL-SLICE: active published-document metadata grouping and service adoption + +date: 2026-06-25 +author: Cline +scope: `pages/api/documents/get-search-document-details.js`, `pages/api/documents/get-search-document-details-paged.js`, `pages/api/documents/get-search-document-types.js`, `actions/services/searchDirectService.js`, `tests/phase22/api-grouping-facade-documents.test.cjs`, `context/api-grouping-adoption-roadmap.md` +type: change +rationale: Implement the second additive API grouping façade vertical slice by grouping the active published-document metadata journey under `pages/api/documents/` and adopting the grouped routes only in the existing common document metadata service layer, while preserving the existing download route and legacy endpoint handlers unchanged. +impact: Runtime behaviour is intended to remain unchanged; grouped document façade routes now cover the active metadata/type journey calls, `actions/services/searchDirectService.js` now targets those grouped routes, legacy endpoint handlers remain canonical, the existing grouped download route remains unchanged, and no migration, route deletion, auth change, CRM query change, hash-link generation change, payload change, or response-contract change has occurred. +status: completed + +Summary: + +- Confirmed the required context was read before implementation: + - `context/api-grouping-plan.md` + - `context/api-grouping-adoption-roadmap.md` + - `context/journey-architecture-map.md` + - `context/api-route-map.md` + - `context/architecture.md` + - `memory-bank/change-log.md` +- Performed a bounded audit of the active published-documents journey across services, API handlers, and frontend components. +- Audit findings established that: + - active document metadata retrieval is surfaced through `components/case/documents.js` + - active document metadata/type calls already route through the common service layer `actions/services/searchDirectService.js` + - published-document downloads already use the grouped runtime route `pages/api/documents/download/[id].js` + - legacy metadata handlers generate `pinswg_hashlink` values that continue to point at the unchanged grouped download route + - `getsearchdocumenthistory_api.js` and `getsearchdocumenthistorypaged_api.js` remain present but were not surfaced in the currently active UI journey and therefore remained outside grouped adoption scope +- Added grouped façade routes under `pages/api/documents/` for the active metadata journey only: + - `get-search-document-details.js` + - `get-search-document-details-paged.js` + - `get-search-document-types.js` +- Implemented each façade as the smallest safe compatibility wrapper: + - import the existing legacy endpoint handler + - delegate `req` and `res` directly to that existing handler +- Updated only the suitable common service layer in `actions/services/searchDirectService.js` to target grouped façade routes for: + - `getSearchDocumentDetails` + - `getSearchDocumentDetailsPaged` + - `getSearchDocumentTypes` +- Left the following unchanged: + - CRM queries + - download behaviour + - hash-link generation + - authentication + - response contracts + - payloads + - file locations of legacy handlers +- Added focused characterization coverage to prove: + - grouped document façade routes delegate correctly + - active document service methods now target grouped document routes + - legacy document endpoint handlers remain present + - active service-layer query-contract cues remain unchanged + - the runtime download path remains `/api/documents/download/[id]` +- Updated the adoption roadmap to record the active documents grouping slice and its bounded scope. + +Validation: + +- Focused validation only intended for this slice: + - `npx eslint actions/services/searchDirectService.js pages/api/documents/get-search-document-details.js pages/api/documents/get-search-document-details-paged.js pages/api/documents/get-search-document-types.js tests/phase22/api-grouping-facade-documents.test.cjs context/api-grouping-adoption-roadmap.md memory-bank/change-log.md` + - `node tests/phase22/api-grouping-facade-documents.test.cjs` +- No repository-wide validation run. + +Follow-ups: + +- The active Documents journey is now a complete vertical grouping slice for the bounded published-document metadata/type path: + - grouped façade routes + - active service-layer adoption + - legacy canonical handlers retained + - unchanged grouped download path + - unchanged behaviour +- Any future expansion beyond this bounded scope should be a separate slice, for example if history/support routes are later proven to be part of an actively adopted user journey. + +--- + ### CL-2026-06-25-API-GROUPING-FACADE-SUBSCRIPTIONS-VERTICAL-SLICE-COMPLETION: watched-case proxy read adoption and scope closure date: 2026-06-25 diff --git a/pages/api/documents/get-search-document-details-paged.js b/pages/api/documents/get-search-document-details-paged.js new file mode 100644 index 00000000..d63621ea --- /dev/null +++ b/pages/api/documents/get-search-document-details-paged.js @@ -0,0 +1,5 @@ +import legacyGetSearchDocumentDetailsPagedHandler from "../endpoint/getsearchdocumentdetailspaged_api"; + +export default async function getSearchDocumentDetailsPagedFacade(req, res) { + return legacyGetSearchDocumentDetailsPagedHandler(req, res); +} diff --git a/pages/api/documents/get-search-document-details.js b/pages/api/documents/get-search-document-details.js new file mode 100644 index 00000000..b0f726d7 --- /dev/null +++ b/pages/api/documents/get-search-document-details.js @@ -0,0 +1,5 @@ +import legacyGetSearchDocumentDetailsHandler from "../endpoint/getsearchdocumentdetails_api"; + +export default async function getSearchDocumentDetailsFacade(req, res) { + return legacyGetSearchDocumentDetailsHandler(req, res); +} diff --git a/pages/api/documents/get-search-document-types.js b/pages/api/documents/get-search-document-types.js new file mode 100644 index 00000000..fc325aed --- /dev/null +++ b/pages/api/documents/get-search-document-types.js @@ -0,0 +1,5 @@ +import legacyGetSearchDocumentTypesHandler from "../endpoint/getsearchdocumentTypes_api"; + +export default async function getSearchDocumentTypesFacade(req, res) { + return legacyGetSearchDocumentTypesHandler(req, res); +} diff --git a/tests/phase22/api-grouping-facade-documents.test.cjs b/tests/phase22/api-grouping-facade-documents.test.cjs new file mode 100644 index 00000000..7ef00bda --- /dev/null +++ b/tests/phase22/api-grouping-facade-documents.test.cjs @@ -0,0 +1,189 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const { loadModule, createRes } = require("../phase21/_shared.cjs"); + +const rootDir = path.resolve(__dirname, "..", ".."); + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +const read = (relativePath) => + fs.readFileSync(path.join(rootDir, relativePath), "utf8"); + +test("documents get-search-document-details facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule( + "pages/api/documents/get-search-document-details.js", + { + legacyGetSearchDocumentDetailsHandler: async (req, res) => { + calls.push({ req, res }); + return res.status(200).json({ ok: true, route: "details" }); + } + } + ); + + const req = { method: "GET", query: { incidentid: "inc-1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); + assert.strictEqual(res.state.statusCode, 200); + assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { + ok: true, + route: "details" + }); +}); + +test("documents get-search-document-details-paged facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule( + "pages/api/documents/get-search-document-details-paged.js", + { + legacyGetSearchDocumentDetailsPagedHandler: async (req, res) => { + calls.push({ req, res }); + return res + .status(200) + .json({ ok: true, route: "details-paged" }); + } + } + ); + + const req = { + method: "GET", + query: { + incidentid: "inc-2", + pageNumber: "1", + orderby: "createdon", + fieldSort: "asc", + showNumberOfRecords: "10", + documentType: "all" + } + }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); + assert.strictEqual(res.state.statusCode, 200); + assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { + ok: true, + route: "details-paged" + }); +}); + +test("documents get-search-document-types facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule("pages/api/documents/get-search-document-types.js", { + legacyGetSearchDocumentTypesHandler: async (req, res) => { + calls.push({ req, res }); + return res + .status(200) + .json([{ pinswg_isharedocumentlocations: 1, count: 2 }]); + } + }); + + const req = { method: "GET", query: { incidentid: "inc-3" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); + assert.strictEqual(res.state.statusCode, 200); + assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), [ + { pinswg_isharedocumentlocations: 1, count: 2 } + ]); +}); + +test("legacy document metadata endpoint files remain present", async () => { + const legacyFiles = [ + "pages/api/endpoint/getsearchdocumentdetails_api.js", + "pages/api/endpoint/getsearchdocumentdetailspaged_api.js", + "pages/api/endpoint/getsearchdocumentTypes_api.js", + "pages/api/endpoint/getsearchdocumenthistory_api.js", + "pages/api/endpoint/getsearchdocumenthistorypaged_api.js", + "pages/api/documents/download/[id].js" + ]; + + legacyFiles.forEach((filePath) => { + assert.strictEqual( + fs.existsSync(path.join(rootDir, filePath)), + true, + filePath + ); + }); +}); + +test("searchDirectService document journey methods now target grouped documents facade routes", async () => { + const source = read("actions/services/searchDirectService.js"); + + assert.match(source, /\/api\/documents\/get-search-document-details\?/); + assert.match( + source, + /\/api\/documents\/get-search-document-details-paged\?/ + ); + assert.match(source, /\/api\/documents\/get-search-document-types\?/); +}); + +test("searchDirectService no longer targets legacy document metadata routes for adopted methods", async () => { + const source = read("actions/services/searchDirectService.js"); + + assert.doesNotMatch( + source, + /\/api\/endpoint\/getsearchdocumentdetails_api/ + ); + assert.doesNotMatch( + source, + /\/api\/endpoint\/getsearchdocumentdetailspaged_api/ + ); + assert.doesNotMatch(source, /\/api\/endpoint\/getsearchdocumentTypes_api/); +}); + +test("document runtime contract cues remain unchanged in searchDirectService", async () => { + const source = read("actions/services/searchDirectService.js"); + + assert.match(source, /incidentid=/); + assert.match(source, /pageNumber=/); + assert.match(source, /orderby=/); + assert.match(source, /fieldSort=/); + assert.match(source, /showNumberOfRecords=/); + assert.match(source, /documentType=/); +}); + +test("document download route remains the runtime download path", async () => { + const metadataDetailsSource = read( + "pages/api/endpoint/getsearchdocumentdetails_api.js" + ); + const metadataPagedSource = read( + "pages/api/endpoint/getsearchdocumentdetailspaged_api.js" + ); + + assert.match(metadataDetailsSource, /\/api\/documents\/download\//); + assert.match(metadataPagedSource, /\/api\/documents\/download\//); +}); + +async function run() { + let failures = 0; + + for (const { name, fn } of tests) { + try { + await fn(); + console.log(`✓ ${name}`); + } catch (error) { + failures += 1; + console.error(`✗ ${name}`); + console.error(error); + } + } + + if (failures > 0) { + process.exit(1); + } +} + +run();