Merged PR 2432: Add myportal API grouping facade read slice
Add myportal API grouping facade read slice Related work items: #23754
This commit is contained in:
@@ -251,7 +251,7 @@ export const getAppealPDFDocument = async (incidentid) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getPortalModuleDetails = async (appealType, caseReference) => {
|
export const getPortalModuleDetails = async (appealType, caseReference) => {
|
||||||
const route = buildFileQuery("/api/endpoint/getportalmoduledetails_api", {
|
const route = buildFileQuery("/api/myportal/get-portal-module-details", {
|
||||||
appealType,
|
appealType,
|
||||||
caseReference: encodeURI(caseReference)
|
caseReference: encodeURI(caseReference)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from "../clients/fileRouteBuilder";
|
} from "../clients/fileRouteBuilder";
|
||||||
|
|
||||||
export const getMyCases = (loggedInUserId) => {
|
export const getMyCases = (loggedInUserId) => {
|
||||||
const route = buildFileQuery("/api/endpoint/getmycases_api", {
|
const route = buildFileQuery("/api/myportal/get-my-cases", {
|
||||||
loggedInUserId
|
loggedInUserId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export const getMyInvolvements = async (loggedInUserId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getMyLPACases = (lpaid) => {
|
export const getMyLPACases = (lpaid) => {
|
||||||
const route = buildFileQuery("/api/endpoint/getmylpacases_api", {
|
const route = buildFileQuery("/api/myportal/get-my-lpa-cases", {
|
||||||
lpaid
|
lpaid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,54 @@ UI journey
|
|||||||
- legacy endpoint handlers remain canonical
|
- legacy endpoint handlers remain canonical
|
||||||
- no route migration, deletion, or contract change has occurred
|
- no route migration, deletion, or contract change has occurred
|
||||||
|
|
||||||
|
#### Additional vertical-slice proof point — My Portal dashboard read / enrichment
|
||||||
|
|
||||||
|
- The active My Portal dashboard read / enrichment journey now has a fifth additive grouping slice for the proven CRM-backed dashboard read/enrichment family only:
|
||||||
|
- grouped façade routes exist under `pages/api/myportal/` for the proven active reads:
|
||||||
|
- `get-my-cases`
|
||||||
|
- `get-my-lpa-cases`
|
||||||
|
- `get-portal-module-details`
|
||||||
|
- the existing owning service-layer adoption is split across the current dashboard read services:
|
||||||
|
- `actions/services/portalDirectService.js` now targets grouped myportal routes for:
|
||||||
|
- `getMyCases`
|
||||||
|
- `getMyLPACases`
|
||||||
|
- `actions/services/caseDirectService.js` now targets the grouped myportal route for:
|
||||||
|
- `getPortalModuleDetails`
|
||||||
|
- legacy endpoint handlers remain present and canonical behind the façade:
|
||||||
|
- `pages/api/endpoint/getmycases_api.js`
|
||||||
|
- `pages/api/endpoint/getmylpacases_api.js`
|
||||||
|
- `pages/api/endpoint/getportalmoduledetails_api.js`
|
||||||
|
- The bounded audit established that the true active My Portal dashboard read/enrichment slice is narrower than the whole dashboard-related API surface:
|
||||||
|
- `pages/myportal/index.js` is the principal authenticated dashboard loader
|
||||||
|
- the active CRM read branch on that page is:
|
||||||
|
- `getMyCases(loggedInUser)` or `getMyLPACases(lpaId)`
|
||||||
|
- `getPortalModuleDetails(collectionName, caseID)` for detail-card enrichment fan-out
|
||||||
|
- watched cases are already covered by the subscriptions slice through `getWatchedCases(loggedInUser)`
|
||||||
|
- representation and awaiting-submission lists on this loader are blob-backed in the active path:
|
||||||
|
- `getRepsFromBlob(thisSession.user.id)`
|
||||||
|
- `getAwaitingSubmissionFromBlob(thisSession.user.id)`
|
||||||
|
- account/bootstrap/auth routes remain upstream supporting concerns rather than part of this grouped read slice:
|
||||||
|
- `getPortalLogin(...)`
|
||||||
|
- `getPersonalAccount(...)`
|
||||||
|
- `getSession(ctx)`
|
||||||
|
- The following routes were intentionally left outside this grouped adoption scope:
|
||||||
|
- `getwatchedcases_api.js`
|
||||||
|
- `getmyrepresentations_api.js`
|
||||||
|
- `getawaitingsubmission_api.js`
|
||||||
|
- storage/draft routes under `pages/api/file/**`
|
||||||
|
- account/auth/session routes
|
||||||
|
- Exclusion reasoning:
|
||||||
|
- watched-case reads are already owned by the completed subscriptions slice
|
||||||
|
- `getmyrepresentations_api.js` and `getawaitingsubmission_api.js` were candidate routes to investigate, but they are not part of the proven active `pages/myportal/index.js` read path, which currently uses blob-backed representation and awaiting-submission retrieval instead
|
||||||
|
- storage/blob routes remain explicitly out of scope for this read-only CRM façade slice
|
||||||
|
- account/bootstrap/auth concerns remain out of scope by requirement and because they are not journey-owned dashboard CRM reads
|
||||||
|
- For this My Portal slice, a complete vertical grouping slice means:
|
||||||
|
- the proven active CRM dashboard read/enrichment family has grouped façade routes
|
||||||
|
- current owning service calls for that family target the grouped routes
|
||||||
|
- subscriptions, storage/draft, and account/auth routes remain unchanged
|
||||||
|
- legacy endpoint handlers remain canonical
|
||||||
|
- no route migration, deletion, contract change, auth change, storage change, or behaviour change has occurred
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Stage 3 — Documentation Maturity
|
### Stage 3 — Documentation Maturity
|
||||||
|
|||||||
@@ -20,6 +20,84 @@ Follow-ups:
|
|||||||
|
|
||||||
### CL-2026-06-25-API-GROUPING-FACADE-SEARCH-VERTICAL-SLICE: active public search results grouping and service adoption
|
### CL-2026-06-25-API-GROUPING-FACADE-SEARCH-VERTICAL-SLICE: active public search results grouping and service adoption
|
||||||
|
|
||||||
|
### CL-2026-06-25-API-GROUPING-FACADE-MYPORTAL-READS-VERTICAL-SLICE: bounded myportal dashboard CRM read/enrichment grouping and service adoption
|
||||||
|
|
||||||
|
date: 2026-06-25
|
||||||
|
author: Cline
|
||||||
|
scope: `pages/api/myportal/get-my-cases.js`, `pages/api/myportal/get-my-lpa-cases.js`, `pages/api/myportal/get-portal-module-details.js`, `actions/services/portalDirectService.js`, `actions/services/caseDirectService.js`, `tests/phase22/api-grouping-facade-myportal-reads.test.cjs`, `context/api-grouping-adoption-roadmap.md`
|
||||||
|
type: change
|
||||||
|
rationale: Implement the next additive API grouping façade vertical slice for the bounded My Portal dashboard read / enrichment journey by grouping only the proven active CRM read/enrichment routes under `pages/api/myportal/` and adopting those grouped routes in the existing owning service layers without changing dashboard storage, auth, watched-case, submission, or account behaviour.
|
||||||
|
impact: Runtime behaviour is intended to remain unchanged; grouped myportal façade routes now cover the proven active dashboard CRM read/enrichment family, legacy endpoint handlers remain canonical, watched-case behaviour remains owned by subscriptions, and no route migration, deletion, contract change, CRM query change, auth/session change, storage/draft change, or submission/orchestration 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/api-route-map.md`
|
||||||
|
- `context/journey-architecture-map.md`
|
||||||
|
- `context/architecture.md`
|
||||||
|
- `memory-bank/change-log.md`
|
||||||
|
- Performed a bounded audit of the active My Portal dashboard read / enrichment journey across the dashboard loader, service layers, and endpoint usage.
|
||||||
|
- Audit findings established that:
|
||||||
|
- the active entry page is `pages/myportal/index.js`
|
||||||
|
- the active owning service layers for the proven CRM read/enrichment path are:
|
||||||
|
- `actions/services/portalDirectService.js` for `getMyCases` / `getMyLPACases`
|
||||||
|
- `actions/services/caseDirectService.js` for `getPortalModuleDetails`
|
||||||
|
- the active CRM-backed dashboard path is:
|
||||||
|
- `getMyCases(loggedInUser)` or `getMyLPACases(lpaId)`
|
||||||
|
- detail-card enrichment fan-out through `getPortalModuleDetails(collectionName, caseID)`
|
||||||
|
- watched cases are already handled by the earlier subscriptions façade slice and were left alone
|
||||||
|
- representation and awaiting-submission data on the active dashboard loader are blob-backed in the current path via:
|
||||||
|
- `getRepsFromBlob(thisSession.user.id)`
|
||||||
|
- `getAwaitingSubmissionFromBlob(thisSession.user.id)`
|
||||||
|
- `getmyrepresentations_api.js` and `getawaitingsubmission_api.js` were investigated but were not proven active in the bounded dashboard loader path for this slice
|
||||||
|
- account/auth/bootstrap routes (`getPortalLogin`, `getPersonalAccount`, `getSession`) remain upstream support concerns and out of scope
|
||||||
|
- Added grouped façade routes under `pages/api/myportal/` for the proven active CRM dashboard family only:
|
||||||
|
- `get-my-cases.js`
|
||||||
|
- `get-my-lpa-cases.js`
|
||||||
|
- `get-portal-module-details.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 existing owning service call sites needed for the bounded slice:
|
||||||
|
- `actions/services/portalDirectService.js`
|
||||||
|
- `getMyCases`
|
||||||
|
- `getMyLPACases`
|
||||||
|
- `actions/services/caseDirectService.js`
|
||||||
|
- `getPortalModuleDetails`
|
||||||
|
- Left the following unchanged:
|
||||||
|
- legacy endpoint handlers
|
||||||
|
- watched-case grouped subscriptions routes
|
||||||
|
- representation routes
|
||||||
|
- awaiting-submission routes
|
||||||
|
- storage/blob routes
|
||||||
|
- account/profile routes
|
||||||
|
- auth/session behaviour
|
||||||
|
- CRM queries and transforms
|
||||||
|
- query parameter names and response contracts
|
||||||
|
- Added focused characterization coverage to prove:
|
||||||
|
- grouped myportal façade routes delegate correctly
|
||||||
|
- legacy endpoint handlers remain present
|
||||||
|
- adopted service methods now target grouped myportal routes
|
||||||
|
- adopted methods no longer target the legacy endpoint URLs directly
|
||||||
|
- request-contract cues remain unchanged
|
||||||
|
- excluded route families remain outside this slice
|
||||||
|
- Updated the adoption roadmap to record the audited scope boundary, included routes, excluded routes, service adoption status, and behaviour-preservation notes for the My Portal dashboard read / enrichment slice.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
- Focused validation only intended for this slice:
|
||||||
|
- `npx eslint actions/services/portalDirectService.js actions/services/caseDirectService.js pages/api/myportal/*.js tests/phase22/api-grouping-facade-myportal-reads.test.cjs`
|
||||||
|
- `node tests/phase22/api-grouping-facade-myportal-reads.test.cjs`
|
||||||
|
- No repository-wide validation run.
|
||||||
|
|
||||||
|
Follow-ups:
|
||||||
|
|
||||||
|
- The bounded My Portal dashboard read / enrichment family is now a complete façade grouping slice only for the proven active CRM dashboard read/enrichment routes adopted here.
|
||||||
|
- Adjacent subscriptions, storage/draft, representation, awaiting-submission, and account/auth support routes remain intentionally outside this slice and should only be considered in separate bounded audits if needed.
|
||||||
|
|
||||||
### CL-2026-06-25-API-GROUPING-FACADE-CASE-DETAILS-VERTICAL-SLICE: bounded case-details read grouping and service adoption
|
### CL-2026-06-25-API-GROUPING-FACADE-CASE-DETAILS-VERTICAL-SLICE: bounded case-details read grouping and service adoption
|
||||||
|
|
||||||
date: 2026-06-25
|
date: 2026-06-25
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import legacyGetMyCasesHandler from "../endpoint/getmycases_api";
|
||||||
|
|
||||||
|
export default async function getMyCasesFacade(req, res) {
|
||||||
|
return legacyGetMyCasesHandler(req, res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import legacyGetMyLpaCasesHandler from "../endpoint/getmylpacases_api";
|
||||||
|
|
||||||
|
export default async function getMyLpaCasesFacade(req, res) {
|
||||||
|
return legacyGetMyLpaCasesHandler(req, res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import legacyGetPortalModuleDetailsHandler from "../endpoint/getportalmoduledetails_api";
|
||||||
|
|
||||||
|
export default async function getPortalModuleDetailsFacade(req, res) {
|
||||||
|
return legacyGetPortalModuleDetailsHandler(req, res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
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("myportal get-my-cases facade delegates to legacy handler", async () => {
|
||||||
|
const calls = [];
|
||||||
|
const mod = loadModule("pages/api/myportal/get-my-cases.js", {
|
||||||
|
legacyGetMyCasesHandler: async (req, res) => {
|
||||||
|
calls.push({ req, res });
|
||||||
|
return res.status(200).json({ ok: true, route: "my-cases" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { method: "GET", query: { loggedInUserId: "user-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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("myportal get-my-lpa-cases facade delegates to legacy handler", async () => {
|
||||||
|
const calls = [];
|
||||||
|
const mod = loadModule("pages/api/myportal/get-my-lpa-cases.js", {
|
||||||
|
legacyGetMyLpaCasesHandler: async (req, res) => {
|
||||||
|
calls.push({ req, res });
|
||||||
|
return res.status(200).json({ ok: true, route: "my-lpa-cases" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { method: "GET", query: { lpaid: "lpa-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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("myportal get-portal-module-details facade delegates to legacy handler", async () => {
|
||||||
|
const calls = [];
|
||||||
|
const mod = loadModule("pages/api/myportal/get-portal-module-details.js", {
|
||||||
|
legacyGetPortalModuleDetailsHandler: async (req, res) => {
|
||||||
|
calls.push({ req, res });
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json({ ok: true, route: "portal-module-details" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
method: "GET",
|
||||||
|
query: {
|
||||||
|
appealType: "pinswg_planningappeals78s",
|
||||||
|
caseReference: "CAS-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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("legacy myportal endpoint handlers remain present", async () => {
|
||||||
|
const legacyFiles = [
|
||||||
|
"pages/api/endpoint/getmycases_api.js",
|
||||||
|
"pages/api/endpoint/getmylpacases_api.js",
|
||||||
|
"pages/api/endpoint/getportalmoduledetails_api.js",
|
||||||
|
"pages/api/endpoint/getmyrepresentations_api.js",
|
||||||
|
"pages/api/endpoint/getawaitingsubmission_api.js"
|
||||||
|
];
|
||||||
|
|
||||||
|
legacyFiles.forEach((filePath) => {
|
||||||
|
assert.strictEqual(
|
||||||
|
fs.existsSync(path.join(rootDir, filePath)),
|
||||||
|
true,
|
||||||
|
filePath
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("active service layers now target grouped myportal routes", async () => {
|
||||||
|
const portalSource = read("actions/services/portalDirectService.js");
|
||||||
|
const caseSource = read("actions/services/caseDirectService.js");
|
||||||
|
|
||||||
|
assert.match(portalSource, /\/api\/myportal\/get-my-cases/);
|
||||||
|
assert.match(portalSource, /\/api\/myportal\/get-my-lpa-cases/);
|
||||||
|
assert.match(caseSource, /\/api\/myportal\/get-portal-module-details/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("adopted service methods no longer target legacy endpoint URLs", async () => {
|
||||||
|
const portalSource = read("actions/services/portalDirectService.js");
|
||||||
|
const caseSource = read("actions/services/caseDirectService.js");
|
||||||
|
|
||||||
|
assert.doesNotMatch(portalSource, /\/api\/endpoint\/getmycases_api/);
|
||||||
|
assert.doesNotMatch(portalSource, /\/api\/endpoint\/getmylpacases_api/);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
caseSource,
|
||||||
|
/\/api\/endpoint\/getportalmoduledetails_api/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("runtime contract strings and parameter names are preserved where structurally testable", async () => {
|
||||||
|
const portalSource = read("actions/services/portalDirectService.js");
|
||||||
|
const caseSource = read("actions/services/caseDirectService.js");
|
||||||
|
|
||||||
|
assert.match(portalSource, /loggedInUserId/);
|
||||||
|
assert.match(portalSource, /lpaid/);
|
||||||
|
assert.match(caseSource, /appealType/);
|
||||||
|
assert.match(caseSource, /caseReference: encodeURI\(caseReference\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("excluded route families remain outside this myportal slice", async () => {
|
||||||
|
const portalSource = read("actions/services/portalDirectService.js");
|
||||||
|
const caseSource = read("actions/services/caseDirectService.js");
|
||||||
|
const pageSource = read("pages/myportal/index.js");
|
||||||
|
|
||||||
|
assert.match(portalSource, /\/api\/subscriptions\/get-watched-cases/);
|
||||||
|
assert.match(pageSource, /getRepsFromBlob\(thisSession.user.id\)/);
|
||||||
|
assert.match(
|
||||||
|
pageSource,
|
||||||
|
/getAwaitingSubmissionFromBlob\(thisSession.user.id\)/
|
||||||
|
);
|
||||||
|
assert.match(portalSource, /\/api\/endpoint\/getmyrepresentations_api/);
|
||||||
|
assert.match(portalSource, /\/api\/endpoint\/getawaitingsubmission_api/);
|
||||||
|
assert.doesNotMatch(caseSource, /\/api\/cases\/get-case"/);
|
||||||
|
assert.doesNotMatch(caseSource, /\/api\/cases\/get-case-by-id/);
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
Reference in New Issue
Block a user