6066 lines
208 KiB
Markdown
6066 lines
208 KiB
Markdown
## Journey Architecture Map
|
||
|
||
This document is a maintainability-focused architecture map for two PEDW journeys:
|
||
|
||
1. Public Search → Case Details
|
||
2. My Portal Dashboard
|
||
|
||
It is a discovery-only slice.
|
||
|
||
It does **not** recommend refactor, implementation, API, state, or folder changes.
|
||
|
||
---
|
||
|
||
### 1. Public Search → Case Details
|
||
|
||
#### Purpose
|
||
|
||
Supports anonymous public discovery of published cases and transition from result browsing into a single case detail view.
|
||
|
||
For maintainers, this journey also includes the optional signed-in enrichment path where case detail pages preload account context for watch/portal-adjacent interactions.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/searchresults.js`
|
||
- `components/search/searchresults.js`
|
||
- `pages/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- adjacent breadcrumb/state context:
|
||
- `components/breadcrumbs.js`
|
||
- `store/currentView/*`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Search results page
|
||
|
||
- `pages/searchresults.js`
|
||
- `getServerSideProps` does **light bootstrap only**
|
||
- captures request IP via `getIP(req)`
|
||
- derives linked-case mode from `query.lk`
|
||
- reads feature flags:
|
||
- `SHOWLOGIN`
|
||
- `SHOWREPRESENTATIONS`
|
||
- dispatches:
|
||
- `setShowReps(showReps, showLoginCheck)`
|
||
- `setSearch(query?.q || "")`
|
||
|
||
Important maintainer note:
|
||
|
||
- This loader does **not** fully hydrate search results itself.
|
||
- Result retrieval is substantially driven in the client/component layer by `components/search/searchresults.js` via paged service calls.
|
||
|
||
##### Search result data bootstrap
|
||
|
||
- `components/search/searchresults.js`
|
||
- reads `searchResultsObj` and `searchDetailsObj` from Redux
|
||
- derives pagination from `@odata.nextLink`
|
||
- for paging/sorting calls:
|
||
- `getBasicSearchPaged(...)`
|
||
- `getAdvancedSearchPaged(...)`
|
||
- after each result fetch, hydrates detail data with:
|
||
- `getSearchDetailsPaged(...)`
|
||
- dispatches:
|
||
- `setSearchResults(...)`
|
||
- `setSearchDetails(...)`
|
||
- `setCurrentPage(...)`
|
||
|
||
##### Case details page
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `getServerSideProps` is the main loader for the detail page
|
||
- captures request IP via `getIP(req)`
|
||
- optionally resolves signed-in portal context:
|
||
- `getSession(ctx)`
|
||
- `getPortalLogin(session.user.email)`
|
||
- `getPersonalAccount(contactid)`
|
||
- `setAccountDetails(accountDetails)`
|
||
- normalises the case identifier from route param to search form
|
||
- retrieves the case through the search family, not a standalone case-by-ticket API:
|
||
- `getBasicSearch(developmentQuery)`
|
||
- `getSearchDetails(searchResultsObj)`
|
||
- conditionally retrieves SIPS-specific enrichments:
|
||
- `getSIPSEvents(...)`
|
||
- `getSIPSMedia(...)`
|
||
- retrieves messages:
|
||
- `getCaseMessage(incidentid)`
|
||
- dispatches:
|
||
- `setSearch(developmentQuery)`
|
||
- `setSearchResults(searchResultsObj)`
|
||
- `setSearchDetails(searchDetailsObj)`
|
||
- `setEventDetails(eventsObj)` when relevant
|
||
- `setMediaDetails(mediaObj)` when relevant
|
||
- `setCurrentReference({...})`
|
||
- redirects to `/404` if the search resolves to zero or multiple matches
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary state slices
|
||
|
||
- `store/search/reducer.js`
|
||
- owns `searchString`
|
||
- used as the retained current search input across search/case navigation
|
||
|
||
- `store/searchOutput/reducer.js`
|
||
- owns:
|
||
- `searchResultsObj`
|
||
- `searchDetailsObj`
|
||
- `documentDetailsObj`
|
||
- `representationsObj`
|
||
- `eventDetailsObj`
|
||
- `mediaDetailsObj`
|
||
- this is the main read model for both results and case-detail rendering
|
||
|
||
- `store/currentView/reducer.js`
|
||
- owns:
|
||
- `caseReference`
|
||
- `currentPage`
|
||
- `showReps`
|
||
- `showLogin`
|
||
- `linkedCaseReferences`
|
||
- `locale`
|
||
- `caseReference` is the key bridge from result selection into detail context
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- only participates when a user is signed in on the case detail route
|
||
- owns signed-in account context:
|
||
- `accountDetails`
|
||
- `loggedinUserId`
|
||
- `containerID`
|
||
|
||
- `store/watchedCases/reducer.js`
|
||
- participates when signed-in users watch/unwatch or manage email notifications from results
|
||
- owns:
|
||
- `watchedCases`
|
||
- `watchedCasesDetails`
|
||
|
||
##### `currentView` usage
|
||
|
||
- `components/search/searchresults.js`
|
||
- sets `currentReference` on case link click
|
||
- updates `currentPage` during pagination/sort
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- sets canonical case reference context for the detail page
|
||
|
||
- `components/breadcrumbs.js`
|
||
- uses `currentView.caseReference` to reconstruct breadcrumb state and origin context
|
||
|
||
##### `accountDetails` usage
|
||
|
||
- Not required for anonymous search or basic case reading
|
||
- Used for optional signed-in enrichment on case pages and watchlist interactions in result views
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary services
|
||
|
||
- `actions/services/searchDirectService.js`
|
||
- `getBasicSearch(...)`
|
||
- `getBasicSearchPaged(...)`
|
||
- `getAdvancedSearchPaged(...)`
|
||
- `getBasicSearchDetails(...)`
|
||
- `getBasicSearchDetailsPaged(...)`
|
||
- `getSearchDocumentDetails(...)`
|
||
- `getLinkedCases(...)`
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `getCaseMessage(...)`
|
||
- `getCase(...)`
|
||
- `getCaseByID(...)`
|
||
- `getSIPSEvents(...)`
|
||
- `getSIPSMedia(...)`
|
||
- `getPortalModuleDetails(...)` for adjacent case-detail enrichment patterns
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPortalLogin(...)`
|
||
- `getPersonalAccount(...)`
|
||
- only used on the optional signed-in branch of case detail bootstrap
|
||
|
||
##### Supporting maintainability helper
|
||
|
||
- `components/utils/index.js`
|
||
- `getSearchDetails(searchResultsObj)`
|
||
- expands result records into case-type-specific detail queries using `collections.json`
|
||
- this is a key maintainability join point because it converts generic search rows into richer case detail payload lookups
|
||
|
||
#### API Layer
|
||
|
||
##### Principal route families
|
||
|
||
- Public search reads
|
||
- `pages/api/endpoint/getbasicsearch_api.js`
|
||
- `pages/api/endpoint/getbasicsearchpaged_api.js`
|
||
- `pages/api/endpoint/getadvancedsearch_api.js`
|
||
- `pages/api/endpoint/getadvancedsearchpaged_api.js`
|
||
|
||
- Search detail expansion / supporting reads
|
||
- `pages/api/endpoint/getbasicsearchdetails_api.js`
|
||
- `pages/api/endpoint/getbasicsearchdetailspaged_api.js`
|
||
- `pages/api/endpoint/getlinkedcases_api.js`
|
||
|
||
- Case-specific reads
|
||
- `pages/api/endpoint/getcase_api.js`
|
||
- `pages/api/endpoint/getcasebyid_api.js`
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
|
||
- Signed-in account bootstrap on the case page
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getpersonalaccount_api.js`
|
||
|
||
##### Route-family characteristics
|
||
|
||
- `getbasicsearchpaged_api.js`
|
||
- helper-oriented CRM relay read
|
||
- validates `searchString`, `orderby`, `fieldSort`, `showNumberOfRecords`
|
||
- uses `relayGet(...)`
|
||
- uses `RELAY_POLICY_SEARCH_PAGED`
|
||
- normalises `@odata.nextLink`
|
||
|
||
- `getcase_api.js`
|
||
- narrow CRM relay read by `incidentID`
|
||
- used as a supporting case lookup shape, though the public ticketnumber route primarily bootstraps via search
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary data source for search results, search detail expansion, case messages, case records, SIPS events, and SIPS media
|
||
- touched because the journey is fundamentally a public case-discovery/read flow
|
||
|
||
- **NextAuth**
|
||
- touched only on the optional signed-in branch of `pages/case/[ticketnumber].js`
|
||
- used to derive current session and then CRM contact context
|
||
|
||
- **Azure Storage**
|
||
- not part of the core public search → case details path in this slice
|
||
|
||
- **Azure Queue**
|
||
- not touched
|
||
|
||
- **GOV.UK Notify**
|
||
- not touched by the core read path
|
||
|
||
- **Local-only processing**
|
||
- Redux hydration and page state transitions
|
||
- breadcrumb/view-state persistence
|
||
- search result highlighting, sorting state, pagination state, and result/detail joining logic in the frontend
|
||
|
||
#### Architectural Flow
|
||
|
||
##### Public search results
|
||
|
||
User
|
||
→ `pages/searchresults.js`
|
||
→ Redux bootstrap (`setSearch`, `setShowReps`)
|
||
→ `components/search/searchresults.js`
|
||
→ `searchDirectService.getBasicSearchPaged` / `getAdvancedSearchPaged`
|
||
→ `pages/api/endpoint/getbasicsearchpaged_api.js` / related search routes
|
||
→ `relayGet(...)`
|
||
→ Azure Relay
|
||
→ Dynamics 365 CRM
|
||
|
||
##### Transition to case details
|
||
|
||
User
|
||
→ case link click in `components/search/searchresults.js`
|
||
→ Redux `setCurrentReference(...)`
|
||
→ `pages/case/[ticketnumber].js` SSR loader
|
||
→ `searchDirectService.getBasicSearch(...)` + `components/utils.getSearchDetails(...)`
|
||
→ supporting `caseDirectService` calls for messages / events / media
|
||
→ endpoint route family
|
||
→ Azure Relay
|
||
→ Dynamics 365 CRM
|
||
|
||
##### Optional signed-in enrichment
|
||
|
||
User session
|
||
→ `getSession(ctx)`
|
||
→ `accountDirectService.getPortalLogin(email)`
|
||
→ `accountDirectService.getPersonalAccount(contactid)`
|
||
→ Redux `accountDetails`
|
||
→ watchlist/account-aware UI behavior
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/searchresults.js`
|
||
- `components/search/searchresults.js`
|
||
- `pages/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `actions/services/searchDirectService.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `components/utils/index.js`
|
||
- `store/search/reducer.js`
|
||
- `store/searchOutput/reducer.js`
|
||
- `store/currentView/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/endpoint/getbasicsearch_api.js`
|
||
- `pages/api/endpoint/getbasicsearchpaged_api.js`
|
||
- `pages/api/endpoint/getbasicsearchdetails_api.js`
|
||
- `pages/api/endpoint/getbasicsearchdetailspaged_api.js`
|
||
- `pages/api/endpoint/getcase_api.js`
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
- `pages/api/endpoint/getlinkedcases_api.js`
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
- `store/accountDetails/reducer.js`
|
||
- `store/watchedCases/reducer.js`
|
||
- `components/breadcrumbs.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- Search contract shape and pagination assumptions (`searchResultsObj`, `@odata.nextLink`)
|
||
- Result-to-detail expansion in `components/utils/index.js`
|
||
- `currentView.caseReference` as the navigation/breadcrumb handoff
|
||
- Case loader assumption that ticketnumber resolves uniquely through the search family
|
||
- Optional signed-in account bootstrap on a nominally public page
|
||
- Watched-case side interactions embedded in search results
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- public-facing and contract-sensitive
|
||
- spans multiple read families rather than a single dedicated case-by-route loader
|
||
- combines SSR and client-driven hydration patterns
|
||
- includes subtle state handoff through Redux rather than only route params
|
||
- optional signed-in behavior adds a second identity/bootstrap branch maintainers must understand
|
||
|
||
---
|
||
|
||
### 2. My Portal Dashboard
|
||
|
||
#### Purpose
|
||
|
||
Supports the authenticated portal landing experience for a signed-in user or LPA user by presenting:
|
||
|
||
- my cases
|
||
- watched cases
|
||
- draft/awaiting-submission items
|
||
- representation draft lists
|
||
- submitted representation-related cards
|
||
- account-contextual portal entry actions
|
||
|
||
This journey is the main authenticated dashboard bootstrap for portal-owned and draft-owned work.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/myportal/index.js`
|
||
- `components/myportal.js`
|
||
- `components/myportal/mycases.js`
|
||
- `components/myportal/watchedcases.js`
|
||
- `components/myportal/myrepresentations.js`
|
||
- `components/myportal/mysubmittedrepresentations.js`
|
||
- `components/myportal/awaitingsubmissionfromblob.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Dashboard page loader
|
||
|
||
- `pages/myportal/index.js`
|
||
- is the principal authenticated bootstrap for the dashboard
|
||
- requires `getSession(ctx)`
|
||
- redirects to `/auth/signin` when the session or session identity is absent
|
||
- resolves CRM contact identity via:
|
||
- `getPortalLogin(thisSession.user.email)`
|
||
- resolves account record via:
|
||
- `getPersonalAccount(loggedInUser)`
|
||
- creates or ensures user storage container via:
|
||
- `createContainerProxy(thisSession.user.id)`
|
||
- branches between user and LPA case retrieval:
|
||
- `getMyCases(loggedInUser)`
|
||
- `getMyLPACases(lpaId)`
|
||
- retrieves mixed-source dashboard datasets:
|
||
- `getRepsFromBlob(thisSession.user.id)`
|
||
- `getWatchedCases(loggedInUser)`
|
||
- `getAwaitingSubmissionFromBlob(thisSession.user.id)`
|
||
- classifies watchlist output using:
|
||
- `splitWatchedCasesBySubmissionState(watchedCases.value)`
|
||
- derives detail cards for multiple lists through bounded parallel `getPortalModuleDetails(...)` calls
|
||
- stores locale and feature flags:
|
||
- `setLocale(locale)`
|
||
- `setShowReps(showReps, showLoginCheck)`
|
||
|
||
##### Dashboard detail expansion
|
||
|
||
- inside `pages/myportal/index.js`, local `getDetails(...)`
|
||
- determines per-list case reference form
|
||
- maps each dashboard record to `getPortalModuleDetails(collectionName, caseID)`
|
||
- this is a key aggregation step because it turns list rows into card/detail-ready case-specific data
|
||
|
||
##### Client-side dashboard navigation state
|
||
|
||
- `components/myportal/topthree.js`
|
||
- sets current case reference before navigating into detail or resume flows
|
||
- refreshes watched cases and awaiting-submission lists after deletion actions
|
||
|
||
- `components/myportal/viewall.js`
|
||
- derives active list from `currentView.currentView.viewKey` or `router.query.key`
|
||
- sets `currentView` and `currentReference` before navigating to case detail or representation edit flows
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary state slices
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- owns:
|
||
- `accountDetails`
|
||
- `loggedinUserId`
|
||
- `containerID`
|
||
- this slice is the main ownership root for:
|
||
- CRM contact identity
|
||
- storage container identity
|
||
- user display/account context
|
||
|
||
- `store/currentView/reducer.js`
|
||
- owns:
|
||
- `currentView`
|
||
- `caseReference`
|
||
- `currentPage`
|
||
- `showReps`
|
||
- `showLogin`
|
||
- `locale`
|
||
- this slice drives which dashboard sub-view is active and what downstream case/representation context should be used
|
||
|
||
- `store/myCases/reducer.js`
|
||
- owns:
|
||
- `myCases`
|
||
- `myCasesDetails`
|
||
|
||
- `store/watchedCases/reducer.js`
|
||
- owns:
|
||
- `watchedCases`
|
||
- `watchedCasesDetails`
|
||
|
||
- `store/awaitingSubmission/reducer.js`
|
||
- owns:
|
||
- `awaitingSubmission`
|
||
- `awaitingSubmissionDetails`
|
||
- `awaitingSubmissionFromBlob`
|
||
|
||
- `store/myRepresentations/*`
|
||
- not re-read in full for this slice, but used by `pages/myportal/index.js` as a primary journey state owner for:
|
||
- `myRepresentations`
|
||
- `myRepresentationsDetails`
|
||
- `mySubmittedReps`
|
||
- `mySubmittedRepsDetails`
|
||
|
||
##### `currentView` usage
|
||
|
||
- `components/myportal/topthree.js`
|
||
- sets `currentReference` before opening case/resume routes
|
||
|
||
- `components/myportal/viewall.js`
|
||
- uses `currentView.viewKey` to determine whether the page is showing:
|
||
- my cases
|
||
- watched cases
|
||
- awaiting submission
|
||
- my representations
|
||
- submitted reps
|
||
- updates `currentView` after list mutations to keep the dashboard sub-view stable
|
||
|
||
- `components/breadcrumbs.js`
|
||
- depends on `currentView` and `caseReference` to reconstruct myportal-origin breadcrumbs
|
||
|
||
##### `accountDetails` usage
|
||
|
||
- `pages/myportal/index.js`
|
||
- populates it at bootstrap time
|
||
|
||
- `components/myportal.js`
|
||
- uses it to determine LPA vs non-LPA rendering
|
||
- uses user name, involvement type, and associated LPA display
|
||
|
||
- `components/myportal/topthree.js` and `components/myportal/viewall.js`
|
||
- use `loggedinUserId` for watched-case mutations and refreshes
|
||
- use `containerID` for draft/blob deletion and resume pathways
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary services
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPortalLogin(...)`
|
||
- `getPersonalAccount(...)`
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `getMyCases(...)`
|
||
- `getMyLPACases(...)`
|
||
- `getWatchedCases(...)`
|
||
- `getWatchedCasesProxy(...)`
|
||
- `getAwaitingSubmission(...)`
|
||
- `getAwaitingSubmissionProxy(...)`
|
||
- `createWatchedCases(...)`
|
||
- `deleteWatchedCases(...)`
|
||
|
||
- `actions/services/documentDirectService.js`
|
||
- `createContainerProxy(...)`
|
||
- `getRepsFromBlob(...)`
|
||
- `getRepsFromBlobProxy(...)`
|
||
- `getAwaitingSubmissionFromBlob(...)`
|
||
- `getAwaitingSubmissionFromBlobProxy(...)`
|
||
- `deleteAwaitingSubmissionsFromBlob(...)`
|
||
- `deleteMyRepresentationsFromBlob(...)`
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `getPortalModuleDetails(...)`
|
||
- `getPortalModuleDetailsProxy(...)`
|
||
- used as the detail enrichment layer for dashboard cards and lists
|
||
|
||
##### Supporting domain helper
|
||
|
||
- `lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js`
|
||
- classifies watchlist records into watched cases vs submitted representations
|
||
- maintainers should treat this as a journey-shaping policy boundary rather than just display logic
|
||
|
||
#### API Layer
|
||
|
||
##### Principal CRM-backed routes
|
||
|
||
- `pages/api/endpoint/getmycases_api.js`
|
||
- `pages/api/endpoint/getmylpacases_api.js`
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getmyrepresentations_api.js`
|
||
- `pages/api/endpoint/getawaitingsubmission_api.js`
|
||
- `pages/api/endpoint/getportalmoduledetails_api.js`
|
||
- `pages/api/endpoint/getpersonalaccount_api.js`
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
|
||
##### Principal storage-backed routes
|
||
|
||
- `pages/api/file/setupcontainer.js`
|
||
- `pages/api/file/getrepsblob.js`
|
||
- `pages/api/file/getawaitingsubmissionfromblob.js`
|
||
- `pages/api/file/getrepsblobproxy.js`
|
||
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
|
||
- `pages/api/file/deleteblobcase.js`
|
||
- `pages/api/file/deleteblobrep.js`
|
||
|
||
##### Route-family characteristics
|
||
|
||
- `getmycases_api.js`
|
||
- helper-oriented CRM relay read
|
||
- requires `loggedInUserId`
|
||
- filters incidents by CRM customer/contact ownership
|
||
- transforms `title` into `pinswg_title` for downstream consumers
|
||
|
||
- `getwatchedcases_api.js`
|
||
- helper-oriented CRM relay read
|
||
- requires `loggedInUserId`
|
||
- reads watchlist rows and expands watched-case metadata
|
||
- flattens nested watched case values into dashboard-friendly fields
|
||
|
||
- `getawaitingsubmissionfromblob.js`
|
||
- storage/blob read route
|
||
- requires `container` and `hash`
|
||
- validates signed hash before blob enumeration
|
||
- reads draft progress files from Azure Storage
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- required entry boundary for the dashboard
|
||
- used to establish `session.user.email` and `session.user.id`
|
||
|
||
- **CRM via Azure Relay**
|
||
- used for:
|
||
- portal login/contact resolution
|
||
- account details
|
||
- my cases
|
||
- LPA cases
|
||
- watched cases
|
||
- portal module details
|
||
- touched because the dashboard mixes user-owned and relationship-owned business records
|
||
|
||
- **Azure Storage**
|
||
- used for:
|
||
- storage container creation/ensuring
|
||
- representation draft blob lists
|
||
- awaiting-submission draft lists
|
||
- deletion of draft case/representation blobs
|
||
- touched because dashboard content includes pre-submission work that is storage-owned rather than CRM-owned
|
||
|
||
- **Azure Queue**
|
||
- not directly touched by the dashboard landing slice reviewed here
|
||
|
||
- **GOV.UK Notify**
|
||
- not part of the dashboard landing bootstrap itself
|
||
|
||
- **Local-only processing**
|
||
- Redux hydration for all dashboard slices
|
||
- current-view selection
|
||
- watched-case classification
|
||
- card/list sorting, list merges, and view transitions
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ `pages/myportal/index.js`
|
||
→ `getSession(ctx)`
|
||
→ `accountDirectService.getPortalLogin(email)`
|
||
→ `accountDirectService.getPersonalAccount(contactid)`
|
||
→ `documentDirectService.createContainerProxy(session.user.id)`
|
||
→ portal/document services fetch CRM-owned and storage-owned lists
|
||
→ `caseDirectService.getPortalModuleDetails(...)` for detail enrichment
|
||
→ Redux slices (`accountDetails`, `myCases`, `watchedCases`, `myRepresentations`, `awaitingSubmission`, `currentView`)
|
||
→ `components/myportal.js` and card/list components
|
||
→ endpoint/file routes
|
||
→ Azure Relay / Azure Storage
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/myportal/index.js`
|
||
- `components/myportal.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `store/accountDetails/reducer.js`
|
||
- `store/currentView/reducer.js`
|
||
- `store/myCases/reducer.js`
|
||
- `store/watchedCases/reducer.js`
|
||
- `store/awaitingSubmission/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/endpoint/getmycases_api.js`
|
||
- `pages/api/endpoint/getmylpacases_api.js`
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getmyrepresentations_api.js`
|
||
- `pages/api/endpoint/getportalmoduledetails_api.js`
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getpersonalaccount_api.js`
|
||
- `pages/api/file/getawaitingsubmissionfromblob.js`
|
||
- `pages/api/file/getrepsblob.js`
|
||
- `pages/api/file/setupcontainer.js`
|
||
- `pages/api/file/deleteblobcase.js`
|
||
- `pages/api/file/deleteblobrep.js`
|
||
- `lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js`
|
||
- `components/myportal/mycases.js`
|
||
- `components/myportal/watchedcases.js`
|
||
- `components/myportal/myrepresentations.js`
|
||
- `components/myportal/mysubmittedrepresentations.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- Session → CRM contact bootstrap via `getPortalLogin(...)`
|
||
- Mixed ownership model:
|
||
- CRM-owned lists
|
||
- Azure-storage-owned draft lists
|
||
- LPA vs non-LPA branching in the page loader
|
||
- `currentView`-driven list/view routing assumptions in `viewall.js`
|
||
- Watchlist mutation/refresh behavior embedded in dashboard components
|
||
- Container identity usage for draft deletion/resume paths
|
||
- Detail enrichment fan-out using `getPortalModuleDetails(...)`
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- authenticated portal-critical journey
|
||
- depends on both identity bootstrap and mixed integration data sources
|
||
- mixes CRM-owned and blob-owned records in one page-level loader
|
||
- multiple Redux slices must stay aligned for correct downstream navigation
|
||
- list cards and view-all pages reuse the same state in several slightly different ways
|
||
|
||
---
|
||
|
||
## Investigation Method
|
||
|
||
### Files reviewed
|
||
|
||
Required context:
|
||
|
||
- `context/architecture.md`
|
||
- `context/api-route-map.md`
|
||
- `context/integration-map.md`
|
||
- `context/portal-api-platform-assessment.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Guardrails/context discipline:
|
||
|
||
- `.clinerules/refactor-branch-rules.md`
|
||
- `GUARDRAILS.md`
|
||
|
||
Journey pages and major components:
|
||
|
||
- `pages/searchresults.js`
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/myportal/index.js`
|
||
- `components/search/searchresults.js`
|
||
- `components/case.js`
|
||
- `components/myportal.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
|
||
Supporting services/helpers:
|
||
|
||
- `actions/services/searchDirectService.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `components/utils/index.js`
|
||
|
||
API handlers:
|
||
|
||
- `pages/api/endpoint/getbasicsearchpaged_api.js`
|
||
- `pages/api/endpoint/getcase_api.js`
|
||
- `pages/api/endpoint/getmycases_api.js`
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/file/getawaitingsubmissionfromblob.js`
|
||
|
||
Redux ownership files:
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- `store/currentView/reducer.js`
|
||
- `store/search/reducer.js`
|
||
- `store/searchOutput/reducer.js`
|
||
- `store/watchedCases/reducer.js`
|
||
- `store/myCases/reducer.js`
|
||
- `store/awaitingSubmission/reducer.js`
|
||
|
||
### Searches performed
|
||
|
||
- `pages`: `getServerSideProps|getInitialProps`
|
||
- `store`: `currentView|accountDetails|search|myportal|watchedCases`
|
||
- `actions/services`: `getBasicSearchPaged|getAdvancedSearchPaged|getCase\(|getCaseByID|getMyCases|getMyRepresentations|getAwaitingSubmission|getWatchedCases`
|
||
- `lib`: `loadMyPortal|resolveMyPortalAuthContext|search|case`
|
||
- `components`: `currentView|accountDetails|getBasicSearch|getCase|getMyCases|getWatchedCases|pinsUser`
|
||
|
||
### Limitations
|
||
|
||
- This slice was intentionally limited to the two requested journeys.
|
||
- It did not trace unrelated journeys such as new appeal, representation submission, auth-only flows, or document-download deep paths beyond adjacent references.
|
||
- It did not execute the application or produce runtime traces.
|
||
- It did not generate a full route inventory.
|
||
- It did not deeply inspect every nested component under case detail or myportal once the primary ownership and integration boundaries were established.
|
||
- Some adjacent state slices, especially `myRepresentations`, were confirmed by usage in the page loader/component layer rather than fully re-read in this slice.
|
||
|
||
---
|
||
|
||
## Recommendations
|
||
|
||
Documentation and understanding only:
|
||
|
||
1. Treat this map as the maintainer-first companion to `context/api-route-map.md`.
|
||
2. When changing either journey, start from the journey entry page and confirm the owning Redux slices before reading deeper API files.
|
||
3. Preserve awareness that both journeys use aggregation patterns rather than single-source page loaders:
|
||
- Public Search → Case Details uses search-family reads plus detail expansion.
|
||
- My Portal Dashboard uses session/bootstrap plus mixed CRM and Azure Storage sources.
|
||
4. Keep identity bootstrap and state ownership explicitly documented in future journey maps, because they are as important to maintainability as the page/component structure.
|
||
5. If this document is extended later, continue documenting by journey and by ownership flow rather than by folder alone.
|
||
|
||
---
|
||
|
||
## Slice 2 — Draft Appeal Creation and Appeal Submission / Finalisation
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- The appeal lifecycle is split across two closely linked but distinct maintainability shapes:
|
||
- **Draft Appeal Creation** is primarily a storage-owned journey rooted in `session.user.id -> container identity`.
|
||
- **Appeal Submission / Finalisation** is an orchestration-owned transition from blob-backed draft state into queue-backed and CRM-backed submitted state.
|
||
- The entry pages for both new and resumed appeals converge on the same page shell and flow components:
|
||
- `pages/newappeal/[appealtypes].js`
|
||
- `pages/myportal/[appealtypes].js`
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- The strongest visible ownership model remains:
|
||
- `NextAuth session.user.id -> Azure Storage container`
|
||
- `pinsUser cookie / CRM contact -> account context and submitted-case ownership context`
|
||
- Submission is not a single direct CRM write from the page layer.
|
||
It visibly passes through:
|
||
- check answers
|
||
- PDF generation / finalisation prep
|
||
- appeal-complete message route
|
||
- Azure Queue message creation
|
||
- downstream submitted-record processing
|
||
|
||
### Draft Appeal Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows an authenticated user to begin an appeal, build it over multiple sections, upload supporting files, save progress, exit, and later resume without immediate submission.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey is the main blob-backed draft lifecycle for appeals and is the clearest place to understand how PEDW uses `session.user.id` as storage/container ownership before CRM submission occurs.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/newappeal/index.js`
|
||
- `components/newappeal/createCase.js`
|
||
- `pages/newappeal/[appealtypes].js`
|
||
- `pages/myportal/[appealtypes].js`
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- `components/newappeal/buildsection.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Draft creation entry
|
||
|
||
- `pages/newappeal/index.js`
|
||
- session-gated via `getSession(ctx)`
|
||
- uses `pinsUser` cookie as CRM contact identity for account lookup
|
||
- loads reference data for starting a draft:
|
||
- `getAppealsTypesForNewAppeal()`
|
||
- `getLPA()`
|
||
- `getPersonalAccount(loggedInUser)`
|
||
- dispatches:
|
||
- `setAppealType(...)`
|
||
- `setLPA(...)`
|
||
- `setLoggedInUserId(loggedInUser)`
|
||
- `setAccountDetails(accountDetails)`
|
||
- `setContainerID(thisSession.user.id)`
|
||
|
||
##### Draft section loader
|
||
|
||
- `lib/newappeal/loadNewAppealPage.js`
|
||
- validates required query params:
|
||
- `appealtypes`
|
||
- `apt`
|
||
- `id`
|
||
- requires `getSession(ctx)`
|
||
- requires `session.user.id` and `session.user.email`
|
||
- requires `pinsUser` cookie presence
|
||
- fetches in parallel:
|
||
- appeal type reference data
|
||
- mandatory fields
|
||
- pick lists
|
||
- `getProgressFromBlob(session.user.id, query.id)`
|
||
- `getPersonalAccount(pinsUser)`
|
||
- reads form XML through `readFormXml(query.appealtypes)`
|
||
|
||
- `pages/newappeal/[appealtypes].js`
|
||
- delegates SSR loading to `loadNewAppealPage(ctx)`
|
||
- hydrates Redux using `hydrateNewAppealStore(...)`
|
||
|
||
##### Draft resume loader
|
||
|
||
- `lib/myportal/loadMyPortalAppealPage.js`
|
||
- validates required query params:
|
||
- `appealtypes`
|
||
- `apt`
|
||
- `casereference`
|
||
- requires `getSession(ctx)`
|
||
- requires `pinsUser` cookie
|
||
- fetches in parallel:
|
||
- appeal type reference data
|
||
- mandatory fields
|
||
- pick lists
|
||
- `getFilesFromBlob(session.user.id, query.casereference)`
|
||
- `getProgressFromBlob(session.user.id, query.casereference)`
|
||
- `getPersonalAccount(pinsUser)`
|
||
- conditionally fetches `getAwaitingSubmissionFromBlob(session.user.id)` when `query.key` is present
|
||
- reads form XML through `readFormXml(query.appealtypes)`
|
||
|
||
- `pages/myportal/[appealtypes].js`
|
||
- delegates SSR loading to `loadMyPortalAppealPage(ctx)`
|
||
- hydrates Redux using `hydrateMyPortalAppealStore(...)`
|
||
|
||
##### Draft bootstrap logic
|
||
|
||
- `lib/newappeal/hydrateNewAppealStore.js`
|
||
- constructs `appealType.caseReference` as:
|
||
- `ticketnumber: query.id`
|
||
- `incidentid: query.id`
|
||
- `caseDetails: blobProgress`
|
||
- dispatches:
|
||
- `setLoggedInUserId(...)`
|
||
- `setLoggedInUserEmail(...)`
|
||
- `setAppealLPA(query.lpa)`
|
||
- `setAppealTypeID(query.apt)`
|
||
- `setCaseReference(...)`
|
||
- `setForm(xmlStr, mandatoryFieldsData, pickListData)`
|
||
- `setAppealType(appealTypeData)`
|
||
- `setContainerID(session.user.id)`
|
||
- `setAccountDetails(accountDetails)`
|
||
|
||
- `lib/myportal/hydrateMyPortalAppealStore.js`
|
||
- builds equivalent case reference state for resume mode
|
||
- additionally dispatches:
|
||
- `setFilesForAppeal(blobList)`
|
||
- `setAwaitingSubmissionFromBlob(...)`
|
||
- `setAwaitingSubmissionDetails(...)`
|
||
- `setCurrentView({ viewName: "Awaiting Submission", viewKey: "awaitingSubmissionDetails" })` when resume is entered from that list context
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/appealType/reducer.js`
|
||
- main appeal-journey owner for:
|
||
- `appealTypeOptions`
|
||
- `appealTypeID`
|
||
- `currentSection`
|
||
- `appealLPA`
|
||
- `caseReference`
|
||
- `formComplete`
|
||
- `documentList`
|
||
- `fileList`
|
||
- `fileCount`
|
||
- `progress`
|
||
|
||
- `store/formData/reducer.js`
|
||
- owns:
|
||
- `formData`
|
||
- `mandatoryFieldsData`
|
||
- `pickListData`
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- owns:
|
||
- `accountDetails`
|
||
- `loggedinUserId`
|
||
- `loggedinUserEmail`
|
||
- `containerID`
|
||
- `containerID` is the strongest visible draft-ownership identifier in the UI/store layer
|
||
|
||
- `store/currentView/reducer.js`
|
||
- supports locale and resume-entry view state
|
||
- used less as the main draft owner than in dashboard journeys, but still participates in:
|
||
- `locale`
|
||
- resume-origin context
|
||
|
||
- `store/awaitingSubmission/reducer.js`
|
||
- participates when a saved draft is resumed from myportal context
|
||
|
||
##### `currentView` usage
|
||
|
||
- `hydrateMyPortalAppealStore(...)` sets `currentView` when the draft resume path originates from awaiting-submission UI
|
||
- component flows use current section progression through `appealType.currentSection` rather than `currentView`
|
||
|
||
##### `accountDetails` usage
|
||
|
||
- provides:
|
||
- CRM contact identity (`loggedinUserId`)
|
||
- email for partial-save/completion emails (`loggedinUserEmail`)
|
||
- container ownership (`containerID`)
|
||
- `components/newappeal/buildsection.js` and `buildchecksection.js` rely on `accountDetails.containerID` to persist and finalise draft material
|
||
|
||
##### Draft ownership state
|
||
|
||
- draft ownership is represented across:
|
||
- `accountDetails.containerID`
|
||
- `appealType.caseReference.ticketnumber`
|
||
- `appealType.caseReference.caseDetails`
|
||
- `appealType.fileList`
|
||
- blob-backed `progress` / file objects retrieved from storage
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/documentDirectService.js`
|
||
- `createContainerProxy(...)`
|
||
- `getProgressFromBlob(...)`
|
||
- `getFilesFromBlob(...)`
|
||
- `uploadFiles(...)`
|
||
- `generateAppealPDF(...)`
|
||
- `deleteAwaitingSubmissionsFromBlob(...)`
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPersonalAccount(...)`
|
||
|
||
- `actions/services/referenceDataService.js`
|
||
- `getAppealsTypesForNewAppeal(...)`
|
||
- `getMandatoryFields(...)`
|
||
- `getPickLists(...)`
|
||
- `getLPA(...)`
|
||
|
||
- `lib/newappeal/journeyEffects.js`
|
||
- `uploadAppealFilesEffect(...)`
|
||
- `sendPartialSaveEmailEffect(...)`
|
||
- `generateAppealPDFEffect(...)`
|
||
- `sendCaseCompleteMessageEffect(...)`
|
||
|
||
##### Major component/service interaction points
|
||
|
||
- `components/newappeal/buildsection.js`
|
||
- orchestrates section progression, progress persistence, partial-save email composition, and upload trigger behavior
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- switches between section form, check answers, and completion views based on `appealType.currentSection`
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- Azure Storage / draft persistence
|
||
- `pages/api/file/setupcontainer.js`
|
||
- `pages/api/file/getprogressobjblob.js`
|
||
- `pages/api/file/getbloblist.js`
|
||
- `pages/api/file/upload.js`
|
||
- `pages/api/file/uploadsinglefile.js`
|
||
- `pages/api/file/deleteblobcase.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `setupcontainer.js`
|
||
- **Azure Storage**
|
||
- creates or ensures the user-owned container using a signed hash-protected path
|
||
|
||
- `getprogressobjblob.js`
|
||
- **Azure Storage**
|
||
- retrieves the most recent draft appeal JSON for a case reference within the container
|
||
|
||
- `getbloblist.js`
|
||
- **Azure Storage**
|
||
- lists uploaded files under a case folder
|
||
|
||
- `upload.js`
|
||
- **Azure Storage**
|
||
- stores draft appeal or representation payload/file material into blob storage
|
||
|
||
- `deleteblobcase.js`
|
||
- **Azure Storage**
|
||
- deletes all blobs under a draft case prefix
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- required because draft ownership begins with `session.user.id`
|
||
|
||
- **CRM**
|
||
- touched during account bootstrap and reference/account lookup, but not yet as the primary owner of the draft itself
|
||
|
||
- **Azure Storage**
|
||
- primary persistence boundary for draft progress, files, case JSON, and generated PDFs before submission
|
||
|
||
- **Azure Queue**
|
||
- not part of draft creation itself
|
||
|
||
- **GOV.UK Notify**
|
||
- touched for partial-save and completion email helper paths in the UI/service layer
|
||
|
||
- **Local processing**
|
||
- form XML parsing
|
||
- progress derivation
|
||
- payload cleanup
|
||
- section/state transitions
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
NextAuth session.user.id
|
||
→ accountDetails.containerID
|
||
→ Azure Storage container
|
||
→ caseReference ticketnumber / casefolderID
|
||
→ draft JSON + uploaded files + case blob
|
||
```
|
||
|
||
Visible characteristics:
|
||
|
||
- storage ownership is strongest at `session.user.id -> containerID`
|
||
- draft identity is then refined by `caseReference` / casefolder prefix inside the container
|
||
- CRM contact identity (`pinsUser`) supports account/bootstrap context but is not the main draft storage key
|
||
|
||
#### Architectural Flows
|
||
|
||
##### Draft creation / save
|
||
|
||
User
|
||
→ `pages/newappeal/index.js`
|
||
→ account/reference bootstrap
|
||
→ `pages/newappeal/[appealtypes].js`
|
||
→ `loadNewAppealPage()`
|
||
→ `hydrateNewAppealStore()`
|
||
→ Redux (`appealType`, `formData`, `accountDetails`)
|
||
→ `components/newappeal/buildsection.js`
|
||
→ `uploadAppealFilesEffect()` / progress persistence behavior
|
||
→ `pages/api/file/upload.js` + `getprogressobjblob.js` + `getbloblist.js`
|
||
→ Azure Storage
|
||
|
||
##### Draft resume
|
||
|
||
User
|
||
→ `pages/myportal/[appealtypes].js`
|
||
→ `loadMyPortalAppealPage()`
|
||
→ `getFilesFromBlob()` + `getProgressFromBlob()`
|
||
→ `hydrateMyPortalAppealStore()`
|
||
→ Redux hydration with blob progress and file list
|
||
→ `components/newappeal/newAppealFlow.js`
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/newappeal/index.js`
|
||
- `pages/newappeal/[appealtypes].js`
|
||
- `pages/myportal/[appealtypes].js`
|
||
- `lib/newappeal/loadNewAppealPage.js`
|
||
- `lib/myportal/loadMyPortalAppealPage.js`
|
||
- `lib/newappeal/hydrateNewAppealStore.js`
|
||
- `lib/myportal/hydrateMyPortalAppealStore.js`
|
||
- `components/newappeal/buildsection.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/azurestorage.js`
|
||
- `store/appealType/reducer.js`
|
||
- `store/formData/reducer.js`
|
||
- `store/accountDetails/reducer.js`
|
||
|
||
##### Adjacent files
|
||
|
||
- `pages/api/file/setupcontainer.js`
|
||
- `pages/api/file/getprogressobjblob.js`
|
||
- `pages/api/file/getbloblist.js`
|
||
- `pages/api/file/upload.js`
|
||
- `pages/api/file/uploadsinglefile.js`
|
||
- `pages/api/file/deleteblobcase.js`
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- `lib/newappeal/journeyEffects.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- session/container ownership assumptions
|
||
- caseReference prefix assumptions in blob naming
|
||
- progress JSON shape versus form XML expectations
|
||
- save/resume state handoff between storage and Redux hydration
|
||
- file-list merging/deduplication in section progress
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- user-critical draft persistence journey
|
||
- strong dependence on storage naming/path conventions
|
||
- loader/bootstrap and hydration behavior must stay aligned
|
||
- save/resume integrity depends on both storage and Redux state consistency
|
||
|
||
### Appeal Submission / Finalisation Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- converts a complete draft appeal into a submitted appeal and confirmation outcome.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey is the clearest orchestration boundary where PEDW transitions from storage-owned draft material into queue-backed submitted processing and CRM-backed case records.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `components/newappeal/buildchecksection.js`
|
||
- `components/newappeal/complete.js`
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- resumed-entry shell:
|
||
- `pages/myportal/[appealtypes].js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
- submission uses the same draft loader/hydration paths described above
|
||
- no separate SSR loader exists just for finalisation
|
||
- the submission preconditions are established by:
|
||
- hydrated `appealType.caseReference`
|
||
- hydrated `accountDetails.containerID`
|
||
- hydrated `formData`
|
||
- hydrated uploaded file list / draft progress state
|
||
|
||
##### Check answers bootstrap
|
||
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- routes to `BuildCheckSection` when `currentSection === sectionCount + 1`
|
||
|
||
- `components/newappeal/buildchecksection.js`
|
||
- assembles final review payload from:
|
||
- `legacyFormState.appealForm.values`
|
||
- `appealType.fileList`
|
||
- `accountDetails.containerID`
|
||
- `appealType.caseReference.ticketnumber`
|
||
- deduplicates file list before finalisation
|
||
- requires explicit user confirmation before submission button becomes active
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/appealType/reducer.js`
|
||
- controls finalisation stage through:
|
||
- `currentSection`
|
||
- `caseReference`
|
||
- `fileList`
|
||
- `formComplete`
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- provides:
|
||
- `containerID`
|
||
- `loggedinUserId`
|
||
- `loggedinUserEmail`
|
||
- `accountDetails.pinswg_typeofinvolvement`
|
||
|
||
- `store/formData/reducer.js`
|
||
- provides mandatory fields/picklist/form shape used to render and validate final answers
|
||
|
||
##### Completion-state transition
|
||
|
||
- `BuildCheckSection.finaliseAppeal()` sets `setCurrentSection(9999)` after PDF generation and finalisation message trigger
|
||
- `components/newappeal/newAppealFlow.js` then switches to `CompleteAppeal`
|
||
- `SECTION_COMPLETE` therefore acts as the visible client-side completion-state boundary
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary services
|
||
|
||
- `actions/services/documentDirectService.js`
|
||
- `generateAppealPDF(...)`
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `sendCaseCompleteMessage(...)`
|
||
- constructs signed URL to `createappealcompletemessage_api`
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `createNewCase(...)`
|
||
- `updateCase(...)`
|
||
- `patchCase(...)`
|
||
- these are the visible CRM mutation helpers adjacent to finalisation ownership transition
|
||
|
||
- `lib/newappeal/journeyEffects.js`
|
||
- `generateAppealPDFEffect(...)`
|
||
- `sendCaseCompleteMessageEffect(...)`
|
||
- `sendCompletionEmailEffect(...)`
|
||
|
||
##### Major finalisation component behavior
|
||
|
||
- `components/newappeal/buildchecksection.js`
|
||
- generates appeal PDF
|
||
- triggers case-complete message effect
|
||
- advances to completion state
|
||
|
||
- `components/newappeal/complete.js`
|
||
- sends completion email on mount
|
||
- reconstructs submitted payload context for display/side-effect continuity
|
||
- visibly represents post-submission confirmation state
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- queue/finalisation/orchestration
|
||
- `pages/api/file/createappealcompletemessage_api.js`
|
||
- `pages/api/file/createappealcompletemessageproxy_api.js`
|
||
|
||
- CRM mutation support
|
||
- `pages/api/endpoint/createcase_api.js`
|
||
- `pages/api/endpoint/updatecase_api.js`
|
||
- `pages/api/endpoint/patchcase_api.js`
|
||
|
||
- draft storage dependencies used during finalisation
|
||
- `pages/api/file/getprogressobjblob.js`
|
||
- `pages/api/file/getbloblist.js`
|
||
- `pages/api/file/upload.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `createappealcompletemessage_api.js`
|
||
- **queue/finalisation**
|
||
- **orchestration**
|
||
- reads draft progress blob and case blob
|
||
- rewrites progress blob with `appealComplete`
|
||
- updates case blob fields
|
||
- may trigger account-type side effect
|
||
- creates queue message for submitted application processing
|
||
|
||
- `createcase_api.js`
|
||
- **CRM relay write**
|
||
- creates incident in CRM and writes case blob to storage
|
||
|
||
- `updatecase_api.js`
|
||
- **CRM relay write**
|
||
- patches appeal-form-specific CRM entity by object ID
|
||
|
||
- `patchcase_api.js`
|
||
- **CRM relay write**
|
||
- patches the incident `servicestage`
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- still the root for container identity and authenticated access to the stored draft
|
||
|
||
- **CRM**
|
||
- visible submitted-record target via `createcase_api`, `updatecase_api`, `patchcase_api`
|
||
- visible account update side effect in `createappealcompletemessage_api`
|
||
|
||
- **Azure Storage**
|
||
- source of truth for draft payload, uploaded files, and case blob before submission completes
|
||
- also persists rewritten completion marker state
|
||
|
||
- **Azure Queue**
|
||
- explicit transition boundary through `createCaseCompleteMessage(...)`
|
||
- carries message containing `appealpath`, `casepath`, and `filespath`
|
||
|
||
- **GOV.UK Notify**
|
||
- used for completion email from `components/newappeal/complete.js`
|
||
|
||
- **Local processing**
|
||
- deduplication, payload cleanup, confirmation-state UI, and client-side section completion transition
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Session identity
|
||
→ container identity
|
||
→ draft ownership
|
||
→ finalisation route reads blob-backed draft state
|
||
→ queue message points to storage artifacts
|
||
→ downstream submitted appeal ownership transitions toward CRM-backed records
|
||
```
|
||
|
||
Visible ownership transition:
|
||
|
||
```text
|
||
Draft ownership (session.user.id container)
|
||
→ appealComplete marker written to draft blob
|
||
→ Azure Queue message created with storage paths
|
||
→ CRM case creation/update pathways become the submitted-record boundary
|
||
```
|
||
|
||
Important maintainer note:
|
||
|
||
- the reviewed code makes the queue handoff explicit, but downstream consumer processing is out of scope of this frontend repository slice.
|
||
- the visible transition point is therefore the queue message plus the adjacent CRM mutation routes, not a fully local end-to-end submitted pipeline implementation inside one file.
|
||
|
||
#### Architectural Flows
|
||
|
||
##### Check answers to finalisation
|
||
|
||
User
|
||
→ `components/newappeal/buildchecksection.js`
|
||
→ assemble final payload + files list
|
||
→ `generateAppealPDFEffect()`
|
||
→ `documentDirectService.generateAppealPDF()`
|
||
→ storage/PDF path
|
||
→ `sendCaseCompleteMessageEffect()`
|
||
→ `portalDirectService.sendCaseCompleteMessage()`
|
||
→ `pages/api/file/createappealcompletemessage_api.js`
|
||
|
||
##### Finalisation orchestration
|
||
|
||
`createappealcompletemessage_api.js`
|
||
→ read draft appeal blob
|
||
→ read case blob
|
||
→ rewrite completion state
|
||
→ persist updated blob state
|
||
→ build queue payload
|
||
→ `actions/azurestorage.createCaseCompleteMessage()`
|
||
→ Azure Queue
|
||
→ downstream submitted processing / CRM transition
|
||
|
||
##### Completion state
|
||
|
||
User
|
||
→ finalise action succeeds
|
||
→ `setCurrentSection(9999)`
|
||
→ `components/newappeal/complete.js`
|
||
→ completion email effect
|
||
→ confirmation UI
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `components/newappeal/buildchecksection.js`
|
||
- `components/newappeal/complete.js`
|
||
- `lib/newappeal/journeyEffects.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `pages/api/file/createappealcompletemessage_api.js`
|
||
- `actions/azurestorage.js`
|
||
|
||
##### Adjacent files
|
||
|
||
- `pages/api/file/createappealcompletemessageproxy_api.js`
|
||
- `pages/api/endpoint/createcase_api.js`
|
||
- `pages/api/endpoint/updatecase_api.js`
|
||
- `pages/api/endpoint/patchcase_api.js`
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- `store/appealType/reducer.js`
|
||
- `store/accountDetails/reducer.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- queue payload creation and storage path assumptions
|
||
- draft blob rewrite before submission handoff
|
||
- case blob update logic
|
||
- involvement/account side effects during finalisation
|
||
- coordination between completion UI state and actual backend handoff
|
||
- mixed storage + queue + CRM orchestration boundary
|
||
|
||
#### Risk Classification
|
||
|
||
**Very High**
|
||
|
||
Reasoning:
|
||
|
||
- it is the core ownership transition boundary in the appeal lifecycle
|
||
- it crosses storage, queue, and CRM concerns
|
||
- subtle regressions can break submission without obviously breaking draft editing
|
||
- completion UI state is near, but not identical to, true backend workflow completion
|
||
|
||
### Ownership Model
|
||
|
||
#### Draft ownership root
|
||
|
||
```text
|
||
NextAuth session.user.id
|
||
→ setContainerID(session.user.id)
|
||
→ storage container identity
|
||
→ casefolderID / ticketnumber prefix
|
||
→ draft appeal JSON
|
||
→ case blob
|
||
→ uploaded files
|
||
```
|
||
|
||
#### Submitted ownership transition
|
||
|
||
```text
|
||
Draft blob state
|
||
→ finalisation route
|
||
→ queue message with storage artifact paths
|
||
→ submitted processing boundary
|
||
→ CRM case / appeal entity ownership context
|
||
```
|
||
|
||
Visible split:
|
||
|
||
- **before submission:** ownership is primarily storage/container scoped
|
||
- **after submission boundary:** ownership becomes increasingly CRM-scoped, with queue handoff as the clearest transition marker visible here
|
||
|
||
### Architectural Flows
|
||
|
||
#### Draft Appeal Creation
|
||
|
||
```text
|
||
User
|
||
→ New Appeal page
|
||
→ SSR loader
|
||
→ Redux hydration
|
||
→ BuildSection
|
||
→ document services
|
||
→ file APIs
|
||
→ Azure Storage
|
||
```
|
||
|
||
#### Draft Resume
|
||
|
||
```text
|
||
User
|
||
→ My Portal resume page
|
||
→ SSR loader
|
||
→ getProgressFromBlob + getFilesFromBlob
|
||
→ Redux hydration
|
||
→ NewAppealFlow
|
||
```
|
||
|
||
#### Appeal Submission / Finalisation
|
||
|
||
```text
|
||
User
|
||
→ Check Answers
|
||
→ generateAppealPDF
|
||
→ createappealcompletemessage_api
|
||
→ Azure Storage draft read/write
|
||
→ Azure Queue
|
||
→ CRM transition boundary
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Draft Appeal Creation
|
||
|
||
- start with:
|
||
- `pages/newappeal/index.js`
|
||
- `pages/newappeal/[appealtypes].js`
|
||
- `lib/newappeal/loadNewAppealPage.js`
|
||
- `components/newappeal/buildsection.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `pages/api/file/{setupcontainer,getprogressobjblob,getbloblist,upload,deleteblobcase}.js`
|
||
|
||
#### Appeal Submission / Finalisation
|
||
|
||
- start with:
|
||
- `components/newappeal/buildchecksection.js`
|
||
- `components/newappeal/complete.js`
|
||
- `lib/newappeal/journeyEffects.js`
|
||
- `pages/api/file/createappealcompletemessage_api.js`
|
||
- `actions/azurestorage.js`
|
||
- `pages/api/endpoint/{createcase_api,updatecase_api,patchcase_api}.js`
|
||
|
||
### Risk Classification
|
||
|
||
- Draft Appeal Creation: **High**
|
||
- Appeal Submission / Finalisation: **Very High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 2
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Journey pages / loaders / hydration:
|
||
|
||
- `pages/newappeal/index.js`
|
||
- `pages/newappeal/[appealtypes].js`
|
||
- `pages/myportal/[appealtypes].js`
|
||
- `lib/newappeal/loadNewAppealPage.js`
|
||
- `lib/myportal/loadMyPortalAppealPage.js`
|
||
- `lib/newappeal/hydrateNewAppealStore.js`
|
||
- `lib/myportal/hydrateMyPortalAppealStore.js`
|
||
|
||
Journey components / effects:
|
||
|
||
- `components/newappeal/newAppealFlow.js`
|
||
- `components/newappeal/buildsection.js`
|
||
- `components/newappeal/buildchecksection.js`
|
||
- `components/newappeal/complete.js`
|
||
- `lib/newappeal/journeyEffects.js`
|
||
|
||
State / service files:
|
||
|
||
- `store/appealType/reducer.js`
|
||
- `store/formData/reducer.js`
|
||
- `store/accountDetails/reducer.js`
|
||
- `store/currentView/reducer.js`
|
||
- `store/awaitingSubmission/reducer.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `actions/azurestorage.js`
|
||
|
||
API handlers:
|
||
|
||
- `pages/api/file/setupcontainer.js`
|
||
- `pages/api/file/getprogressobjblob.js`
|
||
- `pages/api/file/getbloblist.js`
|
||
- `pages/api/file/upload.js`
|
||
- `pages/api/file/deleteblobcase.js`
|
||
- `pages/api/file/createappealcompletemessage_api.js`
|
||
- `pages/api/endpoint/createcase_api.js`
|
||
- `pages/api/endpoint/updatecase_api.js`
|
||
- `pages/api/endpoint/patchcase_api.js`
|
||
|
||
#### Searches performed for Slice 2
|
||
|
||
- `pages`: `newappeal|createappeal|checkanswers|appealtypes|casereference`
|
||
- `lib`: `loadNewAppealPage|loadMyPortalAppealPage|hydrateNewAppealStore|hydrateMyPortalAppealStore|journeyEffects|session.user.id|containerID`
|
||
- `actions/services`: `getProgressFromBlob|getFilesFromBlob|createContainerProxy|sendCaseCompleteMessage|createNewCase|updateCase|patchCase|uploadFiles|generateAppealPDF`
|
||
- `pages/api`: `createappealcompletemessage_api|setupcontainer|getprogressobjblob|getbloblist|uploadsinglefile|upload\.js|deleteblobcase|createcase_api|updatecase_api|patchcase_api`
|
||
- `store`: `appealType|containerID|caseReference|formData|currentView|accountDetails`
|
||
- `components/newappeal`: `check|complete|upload|save|submit|partial|generateAppealPDF|sendCaseCompleteMessage`
|
||
- `actions`: `createCaseCompleteMessage|getCaseBlob|downloadProgressFile|getProgressBlobs|createBlob\(|deleteBlobCase\(|createQueue|QueueClient|sendMessage`
|
||
|
||
#### Limitations for Slice 2
|
||
|
||
- This slice was intentionally limited to draft appeals and appeal finalisation only.
|
||
- Representation journeys were not traced.
|
||
- Downstream queue consumers outside the reviewed frontend repository were not inspected.
|
||
- No runtime execution or queue-processing verification was performed.
|
||
- No implementation changes, tests, or scripts were run because this remained documentation-only discovery.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. The visible draft ownership model is strong at the loader/bootstrap level, but several storage APIs still accept caller-supplied container/casefolder values and rely on signed-path integrity.
|
||
2. Finalisation is an orchestration boundary, not a simple page submit. Maintainers should expect interactions across UI state, storage state, queue handoff, and CRM write helpers.
|
||
3. Completion UI state (`currentSection = 9999`) should not be treated as equivalent to a fully independently verified downstream submitted-record outcome.
|
||
4. Queue creation is visible in this slice; downstream processing behavior is not.
|
||
|
||
### Validation Performed
|
||
|
||
- Re-read all required Slice 2 context files before investigation.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Traced the visible ownership model from:
|
||
- session
|
||
- container
|
||
- draft blob state
|
||
- queue handoff
|
||
- CRM mutation boundary
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Representation Draft Creation and Representation Submission / Finalisation**
|
||
|
||
This would complete the parallel maintainability map for the other major storage-backed and queue-backed submission lifecycle without widening into implementation work.
|
||
|
||
---
|
||
|
||
## Slice 3 — Draft Representation Creation and Representation Submission / Finalisation
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- The representation lifecycle reuses much of the same platform shape as the appeal lifecycle:
|
||
- authenticated session bootstrap
|
||
- blob-backed draft persistence in a `session.user.id` container
|
||
- signed storage routes
|
||
- queue-backed completion handoff
|
||
- The most important structural difference is that representation creation is centred on a **case-linked representation draft** rather than a standalone appeal draft keyed by a new appeal case reference from the start.
|
||
- Representation draft and submission flows rely more heavily on `currentView` representation-specific state than the appeal flow, especially for:
|
||
- `representationCapacity`
|
||
- `representationSubmit`
|
||
- `representationSubmitConfirmation`
|
||
- `representationMessageSent`
|
||
- representation file list state
|
||
- The representation completion flow visibly includes more portal-side side effects than the appeal completion flow:
|
||
- representation involvement creation
|
||
- completion queue message
|
||
- email send
|
||
- watched-case/representation-submitted update
|
||
|
||
### Draft Representation Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows an authenticated portal user to start a representation against a case, save it as a draft, upload supporting files, leave, and later return to continue editing before final submission.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey is the clearest representation-specific example of storage-backed draft ownership using the user container, but with a stronger dependency on case context and representation metadata than the appeal draft flow.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/myportal/representation.js`
|
||
- `lib/representation/pageLoaders.js`
|
||
- `components/representation.js`
|
||
- `components/case/representation/*`
|
||
- adjacent dashboard/list entry points that navigate into it:
|
||
- `components/myportal/viewall.js`
|
||
- `components/myportal/topthree_reps.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Primary page loader
|
||
|
||
- `pages/myportal/representation.js`
|
||
- uses `wrapper.getServerSideProps`
|
||
- delegates SSR bootstrap to `loadRepresentationPage({ store, ctx })`
|
||
|
||
##### Shared representation bootstrap
|
||
|
||
- `lib/representation/pageLoaders.js`
|
||
- `loadRepresentationBootstrap({ ctx })`
|
||
- requires `getSession(ctx)`
|
||
- resolves CRM contact via:
|
||
- `getPortalLogin(session.user.email)`
|
||
- loads account details via:
|
||
- `getPersonalAccount(contactid)`
|
||
- loads blob-backed representation drafts via:
|
||
- `getRepsFromBlob(session.user.id)`
|
||
- derives detail payloads for existing drafts with:
|
||
- `getDetails(myRepresentations, "myRepresentations")`
|
||
- which calls `getCase(...)` and `getPortalModuleDetails(...)`
|
||
|
||
##### New representation bootstrap
|
||
|
||
- `loadNewRepresentation({ store, ctx, bootstrap })`
|
||
- requires `query.case`
|
||
- resolves case context via:
|
||
- `getBasicSearch(query.case)`
|
||
- `getSearchDetails(searchResultsObj)`
|
||
- dispatches:
|
||
- `setContainerID(session.user.id)`
|
||
- `setSearchResults(...)`
|
||
- `setSearchDetails(...)`
|
||
- `setSearch(query.case)`
|
||
- `setCurrentView({ viewName: "My Representations", viewKey: "myRepresentations" })`
|
||
- `setCurrentReference({...})`
|
||
- `setAccountDetails(accountDetails)`
|
||
|
||
##### Existing representation draft bootstrap
|
||
|
||
- `loadExistingRepresentation({ store, ctx, bootstrap })`
|
||
- requires:
|
||
- `query.case`
|
||
- `query.created`
|
||
- resolves case context via:
|
||
- `getBasicSearch(query.case)`
|
||
- `getSearchDetails(searchResultsObj)`
|
||
- finds the existing representation draft by matching `repfile_name === query.created`
|
||
- loads supporting representation files from storage via:
|
||
- `getRepsFilesBlobs(containerID, ticketnumber/caseRef, repfile_name)`
|
||
- dispatches:
|
||
- `setSearchResults(...)`
|
||
- `setSearchDetails(...)`
|
||
- `setContainerID(session.user.id)`
|
||
- `setMyRepresentations(...)`
|
||
- `setMyRepresentationsDetails(...)`
|
||
- `setCurrentReference({... repDetails, filesList ...})`
|
||
- `setRepresentationCapacity(result.representationCapacity)`
|
||
- `setAccountDetails(accountDetails)`
|
||
- `setFilesForRepresentations(repsFileListObj)`
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/currentView/reducer.js`
|
||
- main representation journey owner for:
|
||
- `caseReference`
|
||
- `representationCapacity`
|
||
- `representationSubmit`
|
||
- `representationSubmitConfirmation`
|
||
- `representationMessageSent`
|
||
- `fileList`
|
||
- `locale`
|
||
|
||
- `store/myRepresentations/reducer.js`
|
||
- owns:
|
||
- `myRepresentations`
|
||
- `myRepresentationsDetails`
|
||
- `mySubmittedReps`
|
||
- `mySubmittedRepsDetails`
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- owns:
|
||
- `accountDetails`
|
||
- `loggedinUserId`
|
||
- `containerID`
|
||
- `containerID` is the strongest visible draft-representation storage owner
|
||
|
||
- `store/searchOutput/reducer.js`
|
||
- provides case context to the representation journey through:
|
||
- `searchResultsObj`
|
||
- `searchDetailsObj`
|
||
|
||
##### `currentView` usage
|
||
|
||
- representation draft lifecycle uses `currentView` more directly than the appeal lifecycle for journey state:
|
||
- `caseReference.repDetails`
|
||
- `representationCapacity`
|
||
- `representationSubmit`
|
||
- `representationSubmitConfirmation`
|
||
- `representationMessageSent`
|
||
- representation file list
|
||
- breadcrumb/back-link behaviour also depends on these representation flags and questionnaire state
|
||
|
||
##### `accountDetails` usage
|
||
|
||
- provides:
|
||
- CRM contact identity
|
||
- email address for completion notifications
|
||
- storage container identity for draft retrieval/deletion/completion
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/documentDirectService.js`
|
||
- `getRepsFromBlob(...)`
|
||
- `getRepsFromBlobProxy(...)`
|
||
- `deleteMyRepresentationsFromBlob(...)`
|
||
- `generateRepPDF(...)`
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `getMyRepresentations(...)`
|
||
- `getMyRepresentationsProxy(...)`
|
||
- `getRepresentations(...)`
|
||
- `getRepresentationsProxy(...)`
|
||
- `sendRepCompleteMessage(...)`
|
||
- `setRepInvolvment(...)`
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPortalLogin(...)`
|
||
- `getPersonalAccount(...)`
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `getCase(...)`
|
||
- `getPortalModuleDetails(...)`
|
||
|
||
- `actions/services/searchDirectService.js`
|
||
- `getBasicSearch(...)`
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- storage-backed draft routes
|
||
- `pages/api/file/getrepsblob.js`
|
||
- `pages/api/file/getrepsblobproxy.js`
|
||
- `pages/api/file/deleteblobrep.js`
|
||
- adjacent representation draft JSON update route:
|
||
- `pages/api/file/editRepJson.js`
|
||
|
||
- CRM-backed representation retrieval routes
|
||
- `pages/api/endpoint/getmyrepresentations_api.js`
|
||
- `pages/api/endpoint/getmyrepresentationsproxy_api.js`
|
||
- `pages/api/endpoint/getrepresentations_api.js`
|
||
- `pages/api/endpoint/getrepresentationsproxy_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `getrepsblob.js`
|
||
- **Azure Storage**
|
||
- reads all representation draft JSON blobs in the user container via signed hash validation
|
||
|
||
- `deleteblobrep.js`
|
||
- **Azure Storage**
|
||
- deletes a representation draft subtree using container + casefolder + repfile identifier
|
||
|
||
- `getmyrepresentations_api.js`
|
||
- **CRM relay**
|
||
- retrieves CRM representation records filtered by contact ownership
|
||
|
||
- `getrepresentations_api.js`
|
||
- **CRM relay**
|
||
- retrieves published representations for a case by incident ID
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- required for session bootstrap and the storage container owner
|
||
|
||
- **CRM via Azure Relay**
|
||
- used for contact resolution, account details, case lookup, portal module detail lookup, and representation retrieval
|
||
|
||
- **Azure Storage**
|
||
- primary persistence layer for representation drafts and representation files before submission
|
||
|
||
- **Azure Queue**
|
||
- not part of the draft creation phase itself
|
||
|
||
- **GOV.UK Notify**
|
||
- not a core part of draft creation itself
|
||
|
||
- **Local-only processing**
|
||
- representation draft selection
|
||
- questionnaire/back-link view state
|
||
- derived case/search context joining
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
NextAuth session.user.id
|
||
→ accountDetails.containerID
|
||
→ Azure Storage container
|
||
→ rep draft JSON blobs
|
||
→ representation file subtree
|
||
```
|
||
|
||
Visible distinction from appeals:
|
||
|
||
- appeal drafts are keyed around an appeal case reference being created/progressed
|
||
- representation drafts are keyed around an existing case context plus a `repfile_name` draft identity inside the container
|
||
|
||
#### Architectural Flow
|
||
|
||
##### New representation draft
|
||
|
||
User
|
||
→ `pages/myportal/representation.js`
|
||
→ `loadRepresentationPage()`
|
||
→ `loadRepresentationBootstrap()`
|
||
→ `loadNewRepresentation()`
|
||
→ Redux hydration (`currentView`, `searchResultsObj`, `accountDetails`)
|
||
→ representation components
|
||
→ representation draft persisted to Azure Storage-backed routes
|
||
|
||
##### Existing representation draft resume
|
||
|
||
User
|
||
→ `pages/myportal/representation.js?case=...&state=edit&created=...`
|
||
→ `loadExistingRepresentation()`
|
||
→ blob draft match by `repfile_name`
|
||
→ `getRepsFilesBlobs(...)`
|
||
→ Redux hydration with `repDetails`, `representationCapacity`, and representation file list
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/myportal/representation.js`
|
||
- `lib/representation/pageLoaders.js`
|
||
- `components/representation.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `store/currentView/reducer.js`
|
||
- `store/myRepresentations/reducer.js`
|
||
|
||
##### Adjacent files
|
||
|
||
- `pages/api/file/getrepsblob.js`
|
||
- `pages/api/file/getrepsblobproxy.js`
|
||
- `pages/api/file/deleteblobrep.js`
|
||
- `pages/api/file/editRepJson.js`
|
||
- `pages/api/endpoint/getmyrepresentations_api.js`
|
||
- `pages/api/endpoint/getrepresentations_api.js`
|
||
- `components/case/representation/*`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- draft identity through `repfile_name`
|
||
- combined use of case search context and storage-backed representation context
|
||
- `currentView` representation-specific flags
|
||
- container-scoped file tree handling for representation subfolders
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- sensitive user draft flow
|
||
- depends on both storage ownership and case-linked context
|
||
- relies on several representation-specific state flags that can drift from generic appeal behaviour
|
||
|
||
### Representation Submission / Finalisation Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- converts a representation draft or newly entered representation into a submitted representation outcome for a case.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how representation submission differs from appeal submission by centring on involvement creation, representation queue handoff, and watched-case/submission side effects rather than case creation.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `components/case/representation/representationComplete.js`
|
||
- `pages/myportal/representation.js`
|
||
- `components/representation.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
- uses the same `loadRepresentationPage()` bootstrap as draft creation
|
||
- finalisation-specific client state is carried through `currentView` rather than a separate SSR loader
|
||
- representation completion depends on hydrated:
|
||
- `currentView.caseReference`
|
||
- `currentView.representationCapacity`
|
||
- `currentView.representationMessageSent`
|
||
- `accountDetails.containerID`
|
||
- `repFormData.repfile_name`
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/currentView/reducer.js`
|
||
- main submission-state owner for:
|
||
- `representationSubmit`
|
||
- `representationSubmitConfirmation`
|
||
- `representationMessageSent`
|
||
- `representationCapacity`
|
||
- `caseReference.repDetails`
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- provides:
|
||
- CRM contact identity
|
||
- email address
|
||
- container identity
|
||
|
||
- `store/myRepresentations/reducer.js`
|
||
- stores list-level representation state before and after submission refreshes
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary services
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `sendRepCompleteMessage(...)`
|
||
- `setRepInvolvment(...)`
|
||
- `createWatchedCases(...)` via portal service export path used in the completion component
|
||
|
||
- `actions/services/notifyDirectService.js`
|
||
- send email helper path used by completion flow
|
||
|
||
- `actions/services/documentDirectService.js`
|
||
- `generateRepPDF(...)` where applicable in representation flows
|
||
|
||
- `actions/azurestorage.js`
|
||
- `createRepCompleteMessage(...)`
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- finalisation/orchestration
|
||
- `pages/api/file/createrepcompletemessage_api.js`
|
||
- `pages/api/file/createrepinvolvement_api.js`
|
||
|
||
- adjacent representation mutation/deletion route
|
||
- `pages/api/endpoint/deletemyrepresentations_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `createrepcompletemessage_api.js`
|
||
- **queue/finalisation**
|
||
- triggers queue-backed representation completion handoff using container, case reference, and rep draft id
|
||
|
||
- `createrepinvolvement_api.js`
|
||
- **CRM relay write / orchestration**
|
||
- ensures the contact-to-case involvement relationship exists before/around representation completion
|
||
|
||
- `deletemyrepresentations_api.js`
|
||
- **CRM relay delete**
|
||
- deletes representation records by CRM representation ID
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- still the root of container ownership and authenticated portal identity bootstrap
|
||
|
||
- **CRM via Azure Relay**
|
||
- used for representation visibility, involvement creation, and watched/submitted representation side effects
|
||
|
||
- **Azure Storage**
|
||
- source of representation draft JSON/files prior to completion
|
||
|
||
- **Azure Queue**
|
||
- explicit transition boundary through `createRepCompleteMessage(...)`
|
||
|
||
- **GOV.UK Notify**
|
||
- explicit part of the completion flow via completion email send
|
||
|
||
- **Local-only processing**
|
||
- completion-state guards, questionnaire navigation, and one-time message-sent state handling
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Session identity
|
||
→ container identity
|
||
→ representation draft ownership
|
||
→ completion message route
|
||
→ queue handoff
|
||
→ CRM involvement / representation side effects
|
||
```
|
||
|
||
Visible submitted transition:
|
||
|
||
```text
|
||
Representation draft blob
|
||
→ create rep complete message
|
||
→ Azure Queue message
|
||
→ CRM representation/involvement boundary
|
||
→ watched/submitted status updates
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ representation completion component
|
||
→ `setRepInvolvment(...)`
|
||
→ `sendRepCompleteMessage(...)`
|
||
→ `pages/api/file/createrepcompletemessage_api.js`
|
||
→ `actions/azurestorage.createRepCompleteMessage(...)`
|
||
→ Azure Queue
|
||
→ email send
|
||
→ watched-case/submitted side effect
|
||
→ completion UI
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `components/case/representation/representationComplete.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/file/createrepcompletemessage_api.js`
|
||
- `pages/api/file/createrepinvolvement_api.js`
|
||
- `store/currentView/reducer.js`
|
||
|
||
##### Adjacent files
|
||
|
||
- `pages/api/endpoint/deletemyrepresentations_api.js`
|
||
- `pages/api/endpoint/getmyrepresentations_api.js`
|
||
- `pages/api/endpoint/getrepresentations_api.js`
|
||
- `actions/azurestorage.js`
|
||
- `components/case/representation/*`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- representation involvement sequencing
|
||
- queue handoff for representation completion
|
||
- side effects combined in one completion component
|
||
- `representationMessageSent` guarding versus repeated side effects
|
||
- watched/submitted representation state mutation after completion
|
||
|
||
#### Risk Classification
|
||
|
||
**Very High**
|
||
|
||
Reasoning:
|
||
|
||
- multi-integration workflow
|
||
- combines storage, queue, CRM, and notify concerns
|
||
- more client-side side-effect concentration than the appeal completion flow
|
||
|
||
### Appeal vs Representation Lifecycle Comparison
|
||
|
||
#### Ownership comparison
|
||
|
||
- **Shared**
|
||
- both begin from `NextAuth session.user.id -> container identity`
|
||
- both also carry CRM contact identity via account/bootstrap flows
|
||
|
||
- **Different**
|
||
- appeal draft ownership centres on the appeal case reference and draft case blob
|
||
- representation draft ownership centres on an existing case plus a representation draft identity (`repfile_name`) under the container
|
||
|
||
#### Storage comparison
|
||
|
||
- **Shared**
|
||
- both use Azure Storage for pre-submission draft persistence
|
||
- both use signed storage routes and blob helper utilities
|
||
|
||
- **Different**
|
||
- appeal draft flow uses casefolder-based appeal progress blob + case blob + files
|
||
- representation draft flow uses representation draft JSON blobs plus nested representation file subtrees
|
||
|
||
#### Queue comparison
|
||
|
||
- **Shared**
|
||
- both use queue-backed completion handoff
|
||
- both have explicit completion-message file routes
|
||
|
||
- **Different**
|
||
- appeals queue handoff packages appeal/case/files paths for submitted application processing
|
||
- representations queue handoff packages representation-specific path and file subtree for submitted representation processing
|
||
|
||
#### CRM comparison
|
||
|
||
- **Shared**
|
||
- both eventually transition toward CRM-owned submitted-record outcomes
|
||
- both rely on relay-backed mutation/support routes
|
||
|
||
- **Different**
|
||
- appeal lifecycle is more closely aligned to case creation/update transitions
|
||
- representation lifecycle is more closely aligned to involvement creation and representation submission side effects rather than creating a new appeal case
|
||
|
||
#### Maintainability comparison
|
||
|
||
- **Shared mechanisms**
|
||
- session bootstrap
|
||
- storage container ownership
|
||
- signed file routes
|
||
- queue completion message pattern
|
||
|
||
- **Different maintainability assumptions**
|
||
- appeal lifecycle has a clearer draft → submitted application path anchored by case creation/finalisation
|
||
- representation lifecycle has denser client-side state and more completion-side effect coupling in one component
|
||
- representation lifecycle therefore has slightly higher local workflow complexity even though the platform primitives are shared
|
||
|
||
### Ownership Model
|
||
|
||
#### Representation draft ownership root
|
||
|
||
```text
|
||
NextAuth session.user.id
|
||
→ accountDetails.containerID
|
||
→ Azure Storage container
|
||
→ representation draft blob set
|
||
→ representation file subtree
|
||
```
|
||
|
||
#### Representation submitted transition
|
||
|
||
```text
|
||
Representation draft ownership
|
||
→ completion message route
|
||
→ Azure Queue
|
||
→ CRM involvement / representation boundary
|
||
```
|
||
|
||
### Architectural Flows
|
||
|
||
#### Draft Representation Creation
|
||
|
||
```text
|
||
User
|
||
→ Session
|
||
→ Representation loader
|
||
→ Redux currentView/accountDetails/myRepresentations
|
||
→ Storage draft retrieval / save
|
||
→ Azure Storage
|
||
```
|
||
|
||
#### Representation Submission / Finalisation
|
||
|
||
```text
|
||
User
|
||
→ Representation complete flow
|
||
→ involvement creation
|
||
→ completion message creation
|
||
→ Azure Queue
|
||
→ CRM representation boundary
|
||
→ Notify / completion UI
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Draft Representation Creation
|
||
|
||
- start with:
|
||
- `pages/myportal/representation.js`
|
||
- `lib/representation/pageLoaders.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `pages/api/file/{getrepsblob,getrepsblobproxy,deleteblobrep}.js`
|
||
- `store/currentView/reducer.js`
|
||
- `store/myRepresentations/reducer.js`
|
||
|
||
#### Representation Submission / Finalisation
|
||
|
||
- start with:
|
||
- `components/case/representation/representationComplete.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/file/{createrepcompletemessage_api,createrepinvolvement_api}.js`
|
||
- `pages/api/endpoint/deletemyrepresentations_api.js`
|
||
- `actions/azurestorage.js`
|
||
|
||
### Risk Classification
|
||
|
||
- Draft Representation Creation: **High**
|
||
- Representation Submission / Finalisation: **Very High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 3
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Journey pages / loaders / components:
|
||
|
||
- `pages/myportal/representation.js`
|
||
- `lib/representation/pageLoaders.js`
|
||
- `components/representation.js`
|
||
- `components/case/representation/representationComplete.js`
|
||
|
||
State / service / API files:
|
||
|
||
- `store/currentView/reducer.js`
|
||
- `store/myRepresentations/reducer.js`
|
||
- `actions/services/documentDirectService.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/file/getrepsblob.js`
|
||
- `pages/api/file/deleteblobrep.js`
|
||
- `pages/api/file/createrepcompletemessage_api.js`
|
||
- `pages/api/file/createrepinvolvement_api.js`
|
||
- `pages/api/endpoint/getmyrepresentations_api.js`
|
||
- `pages/api/endpoint/getrepresentations_api.js`
|
||
- `pages/api/endpoint/deletemyrepresentations_api.js`
|
||
|
||
#### Searches performed for Slice 3
|
||
|
||
- `pages`: `representation|repsblob|repcompletemessage|created=|case=|state=edit`
|
||
- `lib`: `loadRepresentationPage|loadExistingRepresentation|loadNewRepresentation|representation|getRepsFromBlob|createRepCompleteMessage|questionnaire|showQuestionnaireSection`
|
||
- `actions/services`: `getRepsFromBlob|getRepsFromBlobProxy|sendRepCompleteMessage|setRepInvolvment|getMyRepresentations|getRepresentations|deleteMyRepresentationsFromBlob|generateRepPDF`
|
||
- `pages/api`: `getrepsblob|editRepJson|deleteblobrep|createrepcompletemessage_api|createrepinvolvement_api|deletemyrepresentations_api|getmyrepresentations_api|getrepresentations_api`
|
||
- `store`: `myRepresentations|representationCapacity|representationSubmit|representationMessageSent|fileList|currentView|accountDetails`
|
||
|
||
#### Limitations for Slice 3
|
||
|
||
- This slice was intentionally limited to representation draft and representation finalisation only.
|
||
- It did not reopen appeal implementation details except where comparison was required.
|
||
- Downstream queue consumers outside this frontend repository were not inspected.
|
||
- No runtime execution or queue-processing verification was performed.
|
||
- No implementation changes, tests, or scripts were run because this remained documentation-only discovery.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. Representation draft state is more distributed across `currentView` flags than the appeal draft lifecycle.
|
||
2. Representation completion currently concentrates several side effects in one completion component, which raises maintenance sensitivity even when behaviour is stable.
|
||
3. Queue creation is visible, but downstream representation processing is not visible in this repository slice.
|
||
4. Comparison conclusions are based on frontend-visible lifecycle behaviour and adjacent API boundaries only.
|
||
|
||
### Validation Performed
|
||
|
||
- Re-read all required Slice 3 context files before investigation.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Traced representation lifecycle from:
|
||
- session
|
||
- blob-backed draft ownership
|
||
- completion queue handoff
|
||
- CRM representation/involvement boundary
|
||
- Compared that lifecycle back to the already documented appeal lifecycle.
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Account Registration and Personal Details / Account Management**
|
||
|
||
This would extend the journey map into the identity/bootstrap side of PEDW and complement the already documented draft/submission lifecycles without widening into implementation work.
|
||
|
||
---
|
||
|
||
## Slice 4 — Account Registration and Personal Details / Account Management
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- These two journeys are the clearest maintainer-facing view of the PEDW identity bootstrap model:
|
||
|
||
```text
|
||
NextAuth session
|
||
→ session.user.email
|
||
→ getPortalLogin(email)
|
||
→ CRM Contact
|
||
→ portal access / dashboard access
|
||
```
|
||
|
||
- **Account Registration** exists to bridge the gap when a valid NextAuth session exists but `getPortalLogin(session.user.email)` returns no CRM contact.
|
||
- **Personal Details / Account Management** depends on that bridge having already succeeded and then operates primarily against a CRM contact identifier stored in Redux.
|
||
- The account journeys therefore strengthen the already documented ownership model by making the `session.user.email -> CRM Contact` transition explicit, rather than only implicit through dashboard or draft loaders.
|
||
- The reviewed account APIs fit a future journey-based grouping model reasonably well at the architecture level, but several routes still carry historical naming/behaviour patterns that should be treated as **future grouping candidates** only, not implementation recommendations.
|
||
|
||
### Account Registration Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows an authenticated user who has signed in successfully but does not yet have a CRM-backed portal account/contact to create that account and become eligible for portal access.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey is the clearest place to understand how PEDW turns an authenticated email identity into a CRM Contact record and then into portal eligibility.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/index.js`
|
||
- `pages/account/register.js`
|
||
- `components/account/registerform.js`
|
||
- `components/account/registerCheck.js`
|
||
- `components/account/registerComplete.js`
|
||
- adjacent auth route:
|
||
- `pages/api/auth/[...nextauth].js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Signed-in / no-CRM-contact detection
|
||
|
||
- `pages/index.js`
|
||
- uses `getServerSideProps`
|
||
- requires `getSession(ctx)` for signed-in portal bootstrap
|
||
- if a session exists:
|
||
- dispatches `setContainerID(session.user.id)`
|
||
- calls `getPortalLogin(session.user.email)`
|
||
- client-side branch then decides:
|
||
- if `loggedInUserId.value` is empty -> redirect to `/account/register?id=<encoded email>`
|
||
- if CRM contact exists -> set `pinsUser` cookie and redirect to `/myportal`
|
||
|
||
##### Registration page loader
|
||
|
||
- `pages/account/register.js`
|
||
- requires `getSession(ctx)`
|
||
- redirects to `/auth/signin` if missing
|
||
- passes `loggedInUserEmail: session.user.email` into the page props
|
||
|
||
##### Registration form bootstrap
|
||
|
||
- `components/account/registerform.js`
|
||
- uses Redux Form with `enableReinitialize`
|
||
- seeds `initialValues.emailaddress1` from `loggedInUserEmail`
|
||
- keeps email field disabled, so the current signed-in email remains the registration identity source in the reviewed flow
|
||
|
||
##### Post-registration bootstrap
|
||
|
||
- registration completion does not itself grant portal access directly
|
||
- visible post-registration bootstrap remains:
|
||
|
||
```text
|
||
User returns through signed-in homepage flow
|
||
→ pages/index.js
|
||
→ getPortalLogin(session.user.email)
|
||
→ CRM contact now exists
|
||
→ pinsUser cookie set
|
||
→ redirect to /myportal
|
||
```
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- owns:
|
||
- `accountDetails`
|
||
- `loggedinUserId`
|
||
- `loggedinUserEmail`
|
||
- `accCr`
|
||
- `containerID`
|
||
|
||
##### Registration-specific state
|
||
|
||
- `accCr`
|
||
- used as the registration completion state marker:
|
||
- `false`
|
||
- `created`
|
||
- `exists`
|
||
- `components/account/register.js` also uses local component state for form/check/complete progression:
|
||
- `registerFormComplete`
|
||
- `accountCreatedComplete`
|
||
|
||
##### `accountDetails` usage
|
||
|
||
- registration completion writes account-creation outcome into Redux through `setAccCr(...)`
|
||
- homepage bootstrap later uses the CRM lookup result, not the registration component state itself, as the source of portal eligibility
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPortalLogin(emailAddress)`
|
||
- `createAccount(formValues)`
|
||
- `getEmailAccountCheck(emailAddress)`
|
||
|
||
##### Service responsibilities in this journey
|
||
|
||
- `getPortalLogin(...)`
|
||
- confirms whether a signed-in email already maps to a CRM contact
|
||
- `getEmailAccountCheck(...)`
|
||
- duplicate email/account existence check before create
|
||
- `createAccount(...)`
|
||
- sends the final CRM contact create request
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getemailaccountcheck_api.js`
|
||
- `pages/api/endpoint/createaccount_api.js`
|
||
- adjacent auth route that routes new users toward registration:
|
||
- `pages/api/auth/[...nextauth].js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `getportallogin_api.js`
|
||
- **CRM relay lookup**
|
||
- signed route
|
||
- strict login/bootstrap lookup by email address
|
||
|
||
- `getemailaccountcheck_api.js`
|
||
- **CRM relay lookup**
|
||
- duplicate account/email existence check
|
||
|
||
- `createaccount_api.js`
|
||
- **CRM relay create**
|
||
- creates a CRM `contacts` record from submitted registration payload
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- **auth/session platform-level**
|
||
- responsible for sign-in flow and `newUser` routing to `/account/register`
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- root of authenticated identity
|
||
- decides whether a user is signed in at all
|
||
- sends new users toward `/account/register`
|
||
|
||
- **CRM via Azure Relay**
|
||
- contact existence lookup
|
||
- duplicate email check
|
||
- account/contact creation
|
||
|
||
- **GOV.UK Notify**
|
||
- touched indirectly through auth sign-in/verification email route family, not the registration form itself
|
||
|
||
- **Azure Storage**
|
||
- not a primary part of registration itself
|
||
- session container ID may already be set in homepage bootstrap before portal access completes
|
||
|
||
- **Local-only processing**
|
||
- registration step UI state
|
||
- check-details transition
|
||
- account-created state presentation
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
NextAuth session
|
||
→ session.user.email
|
||
→ getPortalLogin(email)
|
||
→ no CRM contact found
|
||
→ registration flow
|
||
→ create CRM contact
|
||
→ homepage bootstrap re-check
|
||
→ portal access
|
||
```
|
||
|
||
This journey is therefore the clearest explicit bridge between:
|
||
|
||
- authenticated identity
|
||
- CRM contact identity
|
||
- portal eligibility
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ sign in successfully
|
||
→ `pages/index.js` bootstrap
|
||
→ `getPortalLogin(session.user.email)`
|
||
→ no CRM contact found
|
||
→ `/account/register`
|
||
→ registration form / check / complete
|
||
→ `getEmailAccountCheck(...)`
|
||
→ `createAccount(...)`
|
||
→ CRM `contacts` create
|
||
→ later homepage bootstrap re-check
|
||
→ `pinsUser` cookie + `/myportal`
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/index.js`
|
||
- `pages/account/register.js`
|
||
- `components/account/registerform.js`
|
||
- `components/account/registerCheck.js`
|
||
- `components/account/registerComplete.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getemailaccountcheck_api.js`
|
||
- `pages/api/endpoint/createaccount_api.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- `store/accountDetails/reducer.js`
|
||
- `store/accountDetails/action.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- signed-in/no-CRM-contact detection at homepage bootstrap
|
||
- duplicate email/contact check assumptions
|
||
- registration completion state versus true CRM contact availability
|
||
- `newUser` routing assumptions in NextAuth
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- foundational identity bootstrap journey
|
||
- ties auth/session state to portal business identity
|
||
- regressions could block portal entry for legitimate users
|
||
|
||
### Personal Details / Account Management Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows an authenticated portal user with an existing CRM contact to view and update their personal/account details.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how ongoing account management depends on the already-established CRM contact identity and how that contact identity is then reused for account update routes.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/account/personaldetails.js`
|
||
- `components/account/personaldetails.js`
|
||
- `components/account/personaldetailsCheck.js`
|
||
- `components/account/personaldetailsComplete.js`
|
||
- adjacent account UI:
|
||
- `components/myportal/youraccount.js`
|
||
- legacy/adjacent password flow:
|
||
- `pages/account/changepassword.js`
|
||
- `pages/api/endpoint/updatepassword_api.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Page entry
|
||
|
||
- `pages/account/personaldetails.js`
|
||
- no active SSR account-hydration loader in the reviewed code
|
||
- page is client-side session guarded via `useSession()`:
|
||
- `loading` -> `NoSessionWarning`
|
||
- `unauthenticated` -> redirect to `/auth/signin`
|
||
- also writes `pinsUser` cookie from `props.accountDetails.loggedinUserId`
|
||
|
||
##### State hydration assumption
|
||
|
||
- this page assumes account identity/details have already been hydrated into Redux by earlier portal bootstrap flows, especially through:
|
||
|
||
```text
|
||
NextAuth session
|
||
→ getPortalLogin(session.user.email)
|
||
→ CRM contactid
|
||
→ getPersonalAccount(contactid)
|
||
→ store/accountDetails
|
||
```
|
||
|
||
##### Personal details form bootstrap
|
||
|
||
- `components/account/personaldetails.js`
|
||
- uses Redux Form
|
||
- reads initial account values from Redux-backed props rather than loading them fresh inside the page route
|
||
- disabled email field confirms that account management is not treating email as a freely editable identity source in the reviewed UI flow
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- primary owner for:
|
||
- `accountDetails`
|
||
- `loggedinUserId`
|
||
- `loggedinUserEmail`
|
||
- `containerID`
|
||
|
||
- `store/currentView/reducer.js`
|
||
- only adjacent here for navigation/breadcrumb context, not the main owner of account state
|
||
|
||
##### `accountDetails` usage
|
||
|
||
- `loggedinUserId`
|
||
- acts as the CRM contact identifier for account update actions
|
||
- `accountDetails`
|
||
- supplies the current visible account field values
|
||
- `loggedinUserEmail`
|
||
- supports identity continuity, although the read/update journey itself mostly operates on contact ID plus form payload
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPersonalAccount(contactid)`
|
||
- `updateAccount(contactId, updateBody, ssr)`
|
||
- `getPreferredLanguage(email)`
|
||
- `updatePassword(contactId, newpassword)`
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/getpersonalaccount_api.js`
|
||
- `pages/api/endpoint/updateaccount_api.js`
|
||
- adjacent account-support routes:
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
- `pages/api/endpoint/updatepassword_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `getpersonalaccount_api.js`
|
||
- **CRM relay lookup**
|
||
- retrieves account/contact fields by CRM contact ID
|
||
|
||
- `updateaccount_api.js`
|
||
- **CRM relay update**
|
||
- patches the CRM contact record by contact ID
|
||
|
||
- `getpreferredlanguage_api.js`
|
||
- **CRM relay lookup / support route**
|
||
- resolves preferred language by email address
|
||
|
||
- `updatepassword_api.js`
|
||
- **CRM relay update / historical-adjacent**
|
||
- updates a contact record by contact ID
|
||
- relevant mainly as a legacy or alternate account-update path because the live account flow appears to use `updateaccount_api.js` for password-like updates elsewhere
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth**
|
||
- required for session/auth gate at page entry
|
||
- not the direct account record store
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary account read/update boundary
|
||
|
||
- **GOV.UK Notify**
|
||
- not a main part of personal-details/account management itself
|
||
|
||
- **Azure Storage**
|
||
- not a main part of account management itself
|
||
|
||
- **Local-only processing**
|
||
- check-details transition
|
||
- completion-state routing
|
||
- preferred-language cookie update (`pedw_locale`)
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
NextAuth session
|
||
→ prior portal bootstrap
|
||
→ CRM contactid in Redux
|
||
→ getPersonalAccount(contactid)
|
||
→ account details form
|
||
→ updateAccount(contactId, payload)
|
||
→ CRM contact update
|
||
```
|
||
|
||
This journey therefore depends on the identity bridge having already succeeded:
|
||
|
||
- registration creates the CRM contact if missing
|
||
- dashboard/bootstrap hydrates it
|
||
- personal details reuses it as the account management key
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ `/account/personaldetails`
|
||
→ `useSession()` gate
|
||
→ Redux `accountDetails` already present from prior bootstrap
|
||
→ personal details form / check state
|
||
→ `updateAccount(loggedinUserId, formValues)`
|
||
→ `pages/api/endpoint/updateaccount_api.js`
|
||
→ CRM contact patch
|
||
→ completion view + locale cookie update
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/account/personaldetails.js`
|
||
- `components/account/personaldetails.js`
|
||
- `components/account/personaldetailsCheck.js`
|
||
- `components/account/personaldetailsComplete.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `pages/api/endpoint/getpersonalaccount_api.js`
|
||
- `pages/api/endpoint/updateaccount_api.js`
|
||
- `store/accountDetails/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
- `pages/api/endpoint/updatepassword_api.js`
|
||
- `components/myportal/youraccount.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- dependence on pre-hydrated Redux account state rather than active SSR loading here
|
||
- contact ID trust between client-held Redux state and final API route
|
||
- preferred-language and locale-cookie side effects
|
||
- legacy/parallel password update path ambiguity
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- user-critical account data
|
||
- depends on the session-to-CRM-contact bridge remaining coherent
|
||
- mixes present-day account update flow with legacy-adjacent account/password endpoints
|
||
|
||
### Ownership Model
|
||
|
||
#### Identity bootstrap model reinforced by account journeys
|
||
|
||
```text
|
||
NextAuth session
|
||
→ session.user.email
|
||
→ getPortalLogin(email)
|
||
→ CRM Contact
|
||
→ portal access / dashboard access
|
||
```
|
||
|
||
#### How registration fits
|
||
|
||
```text
|
||
Session exists
|
||
→ no CRM Contact found
|
||
→ registration flow
|
||
→ CRM contact creation
|
||
→ later bootstrap succeeds
|
||
```
|
||
|
||
#### How account management fits
|
||
|
||
```text
|
||
Session exists
|
||
→ CRM Contact already known
|
||
→ Redux accountDetails hydrated
|
||
→ account read/update by contactId
|
||
```
|
||
|
||
These journeys therefore make the identity model explicit in two phases:
|
||
|
||
- **registration** creates the missing CRM side of the bridge
|
||
- **account management** depends on and reuses the completed bridge
|
||
|
||
### Future API Grouping Assessment
|
||
|
||
This section is a **future grouping assessment** only.
|
||
|
||
It is **not an implementation recommendation**.
|
||
|
||
| Current route | Journey owner | Integration touched | Future grouping candidate | Migration caution |
|
||
| ------------------------------------------------ | ------------------------------------- | ----------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||
| `pages/api/endpoint/getportallogin_api.js` | account registration / auth bootstrap | CRM Relay + signed request integrity | candidate for `api/account` or `api/auth` | used both for registration gating and broader auth/bootstrap; boundary ownership spans account and auth |
|
||
| `pages/api/endpoint/getpersonalaccount_api.js` | personal details / account management | CRM Relay | candidate for `api/account` | strongly journey-owned by account/profile reads, but also reused by broader portal bootstrap |
|
||
| `pages/api/endpoint/createaccount_api.js` | account registration | CRM Relay | candidate for `api/account` | clear registration ownership, but current route naming/usage is historical and tied to direct CRM create semantics |
|
||
| `pages/api/endpoint/updateaccount_api.js` | personal details / account management | CRM Relay | candidate for `api/account` | central account mutation route; caution because other journeys or legacy flows may also reuse it |
|
||
| `pages/api/endpoint/getemailaccountcheck_api.js` | account registration | CRM Relay | candidate for `api/account` | strong registration fit, but still part of broader bootstrap/identity support checks |
|
||
| `pages/api/endpoint/getpreferredlanguage_api.js` | auth/session and account support | CRM Relay | candidate for `api/shared` or `api/auth` | supports locale resolution more broadly than account pages alone |
|
||
| `pages/api/endpoint/updatepassword_api.js` | legacy-adjacent account management | CRM Relay | unclear / historical | relevant to account domain, but reviewed flow suggests legacy or alternate-path status |
|
||
| `pages/api/auth/[...nextauth].js` | auth/session platform | NextAuth + Notify + CRM lookup support | candidate for `api/auth` | should remain a platform/auth boundary because it owns session and verification flows, not just account registration |
|
||
| `pages/api/auth/resolve-locale.js` | auth/session locale support | CRM Relay + session/cookie locale context | candidate for `api/auth` or `api/shared` | mixed support behavior; not purely account-owned despite using account identity lookup |
|
||
|
||
#### Classification notes
|
||
|
||
- **candidate for `api/account`**
|
||
- routes whose clearest journey owner is registration or personal-details/account management
|
||
- **candidate for `api/auth`**
|
||
- routes whose clearest owner is session/bootstrap/auth flow, even if they consult CRM account identity
|
||
- **candidate for `api/shared`**
|
||
- routes that support multiple journey families and are not cleanly owned by one journey alone
|
||
- **should remain integration/platform-level**
|
||
- routes whose current boundary is more platform/auth than journey-specific
|
||
- **unclear / historical**
|
||
- routes where the visible live journey ownership is mixed, legacy-shaped, or ambiguous in the reviewed code
|
||
|
||
### Architectural Flows
|
||
|
||
#### Account Registration
|
||
|
||
```text
|
||
User
|
||
→ NextAuth session exists
|
||
→ Homepage bootstrap
|
||
→ getPortalLogin(email)
|
||
→ no CRM contact
|
||
→ Registration page
|
||
→ duplicate email check
|
||
→ create CRM contact
|
||
→ later homepage bootstrap succeeds
|
||
→ portal access
|
||
```
|
||
|
||
#### Personal Details / Account Management
|
||
|
||
```text
|
||
User
|
||
→ Session gate
|
||
→ pre-hydrated CRM contact/accountDetails in Redux
|
||
→ personal details form
|
||
→ updateAccount(contactId, payload)
|
||
→ CRM contact update
|
||
→ completion state
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Account Registration
|
||
|
||
- start with:
|
||
- `pages/index.js`
|
||
- `pages/account/register.js`
|
||
- `components/account/registerform.js`
|
||
- `components/account/registerCheck.js`
|
||
- `components/account/registerComplete.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `pages/api/endpoint/{getportallogin_api,getemailaccountcheck_api,createaccount_api}.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
|
||
#### Personal Details / Account Management
|
||
|
||
- start with:
|
||
- `pages/account/personaldetails.js`
|
||
- `components/account/personaldetails.js`
|
||
- `components/account/personaldetailsCheck.js`
|
||
- `components/account/personaldetailsComplete.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `pages/api/endpoint/{getpersonalaccount_api,updateaccount_api,getpreferredlanguage_api,updatepassword_api}.js`
|
||
- `store/accountDetails/reducer.js`
|
||
|
||
### Risk Classification
|
||
|
||
- Account Registration: **High**
|
||
- Personal Details / Account Management: **High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 4
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/portal-api-platform-assessment.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Journey pages / components:
|
||
|
||
- `pages/index.js`
|
||
- `pages/account/register.js`
|
||
- `pages/account/personaldetails.js`
|
||
- `components/account/registerform.js`
|
||
- `components/account/registerCheck.js`
|
||
- `components/account/registerComplete.js`
|
||
- `components/account/personaldetails.js`
|
||
- `components/account/personaldetailsCheck.js`
|
||
- `components/account/personaldetailsComplete.js`
|
||
|
||
Services / state / API files:
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `store/accountDetails/reducer.js`
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getpersonalaccount_api.js`
|
||
- `pages/api/endpoint/createaccount_api.js`
|
||
- `pages/api/endpoint/updateaccount_api.js`
|
||
- `pages/api/endpoint/getemailaccountcheck_api.js`
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
- `pages/api/endpoint/updatepassword_api.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `pages/api/auth/resolve-locale.js`
|
||
|
||
#### Searches performed for Slice 4
|
||
|
||
- `pages`: `register|personaldetails|changepassword|youraccount|getServerSideProps|getSession\(|getPortalLogin`
|
||
- `components/account`: `register|personaldetails|changepassword|emailaddress1|updateAccount|createAccount|getPersonalAccount`
|
||
- `actions/services`: `createAccount|getPortalLogin|getPersonalAccount|updateAccount|getPreferredLanguage|updatePassword|getEmailAccountCheck`
|
||
- `pages/api`: `getportallogin_api|getpersonalaccount_api|createaccount_api|updateaccount_api|getemailaccountcheck_api|getpreferredlanguage_api|updatepassword_api|resolve-locale|nextauth`
|
||
- `store`: `accountDetails|loggedinUserId|loggedinUserEmail|setAccountDetails|setLoggedInUserId|setLoggedInUserEmail|currentView`
|
||
|
||
#### Limitations for Slice 4
|
||
|
||
- This slice was intentionally limited to registration and account-management journeys only.
|
||
- It did not reopen dashboard, appeal, or representation journeys except where identity/bootstrap continuity required it.
|
||
- The future grouping section is a classification exercise only.
|
||
- No runtime execution or auth-flow verification was performed.
|
||
- No implementation changes, tests, or scripts were run because this remained documentation-only discovery.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. Registration completion state and actual portal eligibility are not identical; the visible portal-access transition still depends on a later homepage bootstrap re-check.
|
||
2. Personal details page entry relies on client session gating and pre-hydrated Redux account state more than on active SSR account loading in the reviewed route.
|
||
3. Several account-support APIs participate in both account and auth/session concerns, so future grouping ownership is architectural classification only, not a change proposal.
|
||
4. `updatepassword_api.js` appears relevant as a legacy or alternate account-update path and should be treated cautiously in grouping assessments.
|
||
|
||
### Validation Performed
|
||
|
||
- Re-read all required Slice 4 context files before investigation.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Traced the account identity model from:
|
||
- NextAuth session
|
||
- email-based portal login lookup
|
||
- CRM contact creation/read/update
|
||
- post-registration portal bootstrap
|
||
- Classified future grouping candidates using journey ownership, integration touched, and migration caution only.
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Authentication / Sign-In and Notifications / Email**
|
||
|
||
This would extend the journey map into the cross-cutting auth/communication layer that supports registration, portal entry, and user-facing lifecycle communications without widening into implementation work.
|
||
|
||
---
|
||
|
||
## Slice 5 — Authentication / Sign-In and Notifications / Email
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- Authentication / Sign-In is a cross-cutting support journey centred on `pages/api/auth/[...nextauth].js`, but its practical architecture also includes:
|
||
- locale pre-resolution in `pages/auth/signin.js`
|
||
- CRM preferred-language / contact lookup support
|
||
- homepage bootstrap in `pages/index.js`
|
||
- registration redirect when session exists but CRM contact does not
|
||
- sign-out cleanup via `lib/auth/sessionClient.js`
|
||
- Notifications / Email is not one single journey shape.
|
||
It visibly contains two maintainability forms:
|
||
- a **thin direct Notify send** path via `pages/api/email/notify.js`
|
||
- a **broader aggregation/orchestration** path via `pages/api/email/getall.js` and supporting email data routes
|
||
- GOV.UK Notify is used in two distinct ways:
|
||
- as an **auth-support integration** for passwordless sign-in emails
|
||
- as a **business-notification integration** for completion emails and watchlist/batch updates
|
||
- The strongest visible identity bridge across both journeys remains:
|
||
- `NextAuth session.user.email -> getPortalLogin(email) -> CRM contact / preferred language`
|
||
- This slice does **not** reopen the completed Authorization Architecture Assessment.
|
||
The focus here is how auth and email fit into journey architecture, ownership, and maintainer change-entry.
|
||
|
||
### Authentication / Sign-In Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a user to start a passwordless sign-in flow, receive a verification email, complete callback handling, establish a session, and then continue into portal bootstrap or registration.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey is the clearest cross-cutting entry into authenticated portal behaviour because it joins:
|
||
- sign-in UI
|
||
- locale handling
|
||
- verification-email delivery
|
||
- session establishment
|
||
- CRM contact/bootstrap lookup
|
||
- registration redirect for first-time users
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/auth/signin.js`
|
||
- `pages/auth/verify-request.js`
|
||
- `pages/auth/error.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- homepage bootstrap after callback:
|
||
- `pages/index.js`
|
||
- sign-out / reset touchpoints directly relevant to auth continuity:
|
||
- `components/header.js`
|
||
- `components/myportal/servicebanner.js`
|
||
- `lib/auth/sessionClient.js`
|
||
- `pages/logout.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Sign-in page entry
|
||
|
||
- `pages/auth/signin.js`
|
||
- gated by `SHOWLOGIN`
|
||
- obtains `csrfToken` through `getCsrfToken(context)`
|
||
- builds callback URL from host/protocol and incoming `callbackUrl`
|
||
- appends `locale` to the callback URL before rendering
|
||
|
||
##### Locale resolution before email sign-in submit
|
||
|
||
- `pages/auth/signin.js`
|
||
- intercepts form submit in `handleSubmit(...)`
|
||
- POSTs to `pages/api/auth/resolve-locale.js` with:
|
||
- entered email
|
||
- current UI locale
|
||
- writes `pedw_locale` cookie
|
||
- rewrites the hidden callback URL to include resolved locale before posting to `/api/auth/signin/email`
|
||
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- resolves fallback locale from request/body/cookie
|
||
- if email is present, calls `getPortalLogin(email)`
|
||
- derives locale from CRM `pinswg_preferredlanguage` when available
|
||
- otherwise falls back to request locale
|
||
|
||
##### Verify-request page
|
||
|
||
- `pages/auth/verify-request.js`
|
||
- lightweight page with `getCsrfToken(context)` only
|
||
- presents the check-email state after verification email request
|
||
- keeps locale continuity through normal Next.js locale routing rather than extra bootstrap
|
||
|
||
##### NextAuth session and callback bootstrap
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- defines the NextAuth boundary via `NextAuth(req, res, authOptions(req, res))`
|
||
- derives request locale using:
|
||
- direct query/body locale
|
||
- `pedw_locale` cookie
|
||
- callback URL locale parsing
|
||
- callback cookie fallback
|
||
- resolves effective locale by attempting CRM preferred-language lookup first and request locale second
|
||
- customises:
|
||
- `signIn`
|
||
- `verifyRequest`
|
||
- `error`
|
||
- `newUser`
|
||
page paths by locale
|
||
- uses Prisma adapter and database session strategy
|
||
- rewrites external redirects through the locale-aware redirect callback
|
||
|
||
##### Verification email generation
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `EmailProvider.sendVerificationRequest(...)`
|
||
- builds localized verification URL
|
||
- selects EN/CY Notify template
|
||
- sends sign-in email through GOV.UK Notify
|
||
|
||
##### Post-auth portal bootstrap relationship
|
||
|
||
- `pages/index.js`
|
||
- calls `getSession(ctx)` during SSR
|
||
- if session exists:
|
||
- stores `session.user.id` into `containerID`
|
||
- calls `getPortalLogin(session.user.email)`
|
||
- if CRM contact exists:
|
||
- writes `pinsUser` cookie from CRM `contactid`
|
||
- redirects to `/myportal`
|
||
- if CRM contact does not exist:
|
||
- redirects to `/account/register?id=<hashed email>`
|
||
|
||
##### Logout / reset behaviour directly relevant to auth continuity
|
||
|
||
- `lib/auth/sessionClient.js`
|
||
- `clearSessionArtifacts()` clears:
|
||
- localStorage
|
||
- `next-auth.csrf-token`
|
||
- callback URL cookies
|
||
- `pedw_locale`
|
||
- `pinsUser`
|
||
- `performPortalSignOut(...)` triggers NextAuth `signOut(...)` with locale-aware callback URL
|
||
|
||
- `components/header.js`
|
||
- `components/myportal/servicebanner.js`
|
||
- both call `performPortalSignOut(locale)`
|
||
|
||
- `pages/logout.js`
|
||
- renders logged-out confirmation page
|
||
- resets Redux account state via `setLogout()` on mount
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary ownership layers
|
||
|
||
- **NextAuth session / Prisma-backed persistence**
|
||
- owns whether the user is authenticated
|
||
- owns `session.user.id`
|
||
- owns `session.user.email`
|
||
|
||
- **Cookie-level supporting state**
|
||
- `pedw_locale`
|
||
- preserves locale continuity across sign-in and callback
|
||
- `pinsUser`
|
||
- stores CRM `contactid` after homepage bootstrap succeeds
|
||
- NextAuth callback cookies
|
||
- preserve callback routing context during sign-in flow
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- not the owner of authentication itself
|
||
- becomes the owner of post-auth portal identity context after bootstrap:
|
||
- `loggedinUserId`
|
||
- `loggedinUserEmail`
|
||
- `containerID`
|
||
- `accountDetails`
|
||
|
||
- `store/currentView/reducer.js`
|
||
- participates through locale-related UI context rather than owning session state directly
|
||
|
||
##### Ownership note
|
||
|
||
- auth ownership is therefore split between:
|
||
- **platform auth/session state** in NextAuth/Prisma
|
||
- **journey continuation state** in cookies and homepage/bootstrap redirects
|
||
- **portal business identity state** after CRM bootstrap succeeds
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPortalLogin(emailAddress)`
|
||
- `getPreferredLanguage(email)`
|
||
|
||
##### Supporting auth helpers
|
||
|
||
- `lib/auth/sessionClient.js`
|
||
- `buildSignedOutCallbackUrl(locale)`
|
||
- `clearSessionArtifacts()`
|
||
- `performPortalSignOut(...)`
|
||
|
||
##### Journey role of services
|
||
|
||
- `getPortalLogin(...)`
|
||
- is the main bridge from session email to CRM contact/bootstrap state
|
||
- `getPreferredLanguage(...)`
|
||
- supports language-sensitive mail or locale decisions in adjacent flows
|
||
- `sessionClient`
|
||
- centralises sign-out/reset behaviour across public and portal surfaces
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- adjacent supporting routes directly relevant to auth bootstrap:
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- **auth/session platform route**
|
||
- owns callback, verification-email, redirect, and session behaviour
|
||
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- **auth/session support route**
|
||
- mixed local utility + CRM lookup for locale selection before sign-in submit
|
||
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- **CRM relay lookup / auth bootstrap support**
|
||
- resolves portal contact existence and preferred-language-bearing contact summary by email
|
||
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
- **CRM relay lookup / shared support**
|
||
- resolves preferred language by email
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **NextAuth + Prisma**
|
||
- primary authentication/session boundary
|
||
- owns verification token and database session handling
|
||
|
||
- **GOV.UK Notify**
|
||
- sends passwordless sign-in email
|
||
|
||
- **CRM via Azure Relay**
|
||
- used for preferred-language lookup and portal-contact existence lookup
|
||
- touched because sign-in completion alone is not enough for portal bootstrap; CRM identity still determines portal continuity
|
||
|
||
- **Azure Storage**
|
||
- not part of sign-in itself
|
||
- becomes relevant immediately after successful auth because homepage bootstrap sets container ownership from `session.user.id`
|
||
|
||
- **Local-only processing**
|
||
- callback URL rewriting
|
||
- locale cookie management
|
||
- sign-out artifact clearing
|
||
- post-auth redirect branching
|
||
|
||
#### Ownership / Identity Model
|
||
|
||
```text
|
||
User email
|
||
→ sign-in request
|
||
→ NextAuth verification flow
|
||
→ session.user.email + session.user.id
|
||
→ getPortalLogin(email)
|
||
→ CRM contact found or not found
|
||
→ pinsUser cookie / registration redirect
|
||
→ portal bootstrap continuation
|
||
```
|
||
|
||
This journey makes visible three distinct but linked identity layers:
|
||
|
||
- **authentication identity**
|
||
- `session.user.email`
|
||
- `session.user.id`
|
||
- **business identity**
|
||
- CRM `contactid` from `getPortalLogin(email)`
|
||
- **portal continuity state**
|
||
- `pinsUser`
|
||
- `pedw_locale`
|
||
- callback URL state
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ `pages/auth/signin.js`
|
||
→ `pages/api/auth/resolve-locale.js`
|
||
→ `/api/auth/signin/email`
|
||
→ `pages/api/auth/[...nextauth].js`
|
||
→ GOV.UK Notify verification email
|
||
→ `/api/auth/callback/email`
|
||
→ NextAuth session created
|
||
→ `pages/index.js` SSR bootstrap
|
||
→ `getPortalLogin(session.user.email)`
|
||
→ CRM contact exists?
|
||
→ yes: `pinsUser` + `/myportal`
|
||
→ no: `/account/register`
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/auth/signin.js`
|
||
- `pages/auth/verify-request.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- `pages/index.js`
|
||
- `lib/auth/sessionClient.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/auth/error.js`
|
||
- `components/header.js`
|
||
- `components/myportal/servicebanner.js`
|
||
- `pages/logout.js`
|
||
- `pages/account/register.js`
|
||
- `store/accountDetails/reducer.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- locale resolution before and during callback handling
|
||
- verification-email URL rewriting and EN/CY template selection
|
||
- homepage bootstrap distinction between:
|
||
- authenticated session exists
|
||
- CRM contact exists
|
||
- registration required
|
||
- callback URL / redirect continuity
|
||
- sign-out artifact cleanup across session, locale, and cached CRM-contact continuity
|
||
|
||
#### Risk Classification
|
||
|
||
**Very High**
|
||
|
||
Reasoning:
|
||
|
||
- foundational cross-cutting entry to authenticated portal behaviour
|
||
- combines NextAuth, Notify, CRM lookup, locale continuity, and registration branching
|
||
- regressions can block sign-in, misroute locale, or break portal bootstrap for all authenticated journeys
|
||
|
||
### Notifications / Email Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- sends user-facing transactional and update emails including:
|
||
- auth sign-in emails
|
||
- appeal/representation completion emails
|
||
- watchlist and batch update emails
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how PEDW email behaviour ranges from simple template sends to orchestration routes that collect CRM, document, and event data before building outgoing Notify payloads.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- direct send entry:
|
||
- `pages/api/email/notify.js`
|
||
- auth-support email send embedded in:
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- business completion callers:
|
||
- `components/newappeal/complete.js`
|
||
- `components/case/representation/representationComplete.js`
|
||
- watchlist/email-notification signup touchpoints:
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- aggregation/batch routes:
|
||
- `pages/api/email/getall.js`
|
||
- `pages/api/email/getdocuments.js`
|
||
- `pages/api/email/getevents.js`
|
||
- `pages/api/email/getmailinglist.js`
|
||
- `pages/api/email/getcaseref.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Thin direct Notify send
|
||
|
||
- `actions/services/notifyDirectService.js`
|
||
- packages:
|
||
- `templateId`
|
||
- `emailAddress`
|
||
- `reference`
|
||
- `personalisation`
|
||
- POSTs to `/api/email/notify`
|
||
|
||
- `pages/api/email/notify.js`
|
||
- validates `emailAddress`
|
||
- for `reference === "PEDW-NEW-CASEREF"`, optionally resolves CRM preferred language before final template selection
|
||
- sends email via GOV.UK Notify
|
||
|
||
##### Completion email callers
|
||
|
||
- `components/newappeal/complete.js`
|
||
- derives EN/CY completion template ID from locale
|
||
- sends completion email from client-side completion effect path
|
||
- uses logged-in user email and case reference personalisation
|
||
|
||
- `components/case/representation/representationComplete.js`
|
||
- derives EN/CY and SIPS/non-SIPS template IDs
|
||
- sends representation completion email
|
||
- does so alongside representation completion side effects:
|
||
- involvement
|
||
- completion message
|
||
- watched-case creation/update
|
||
|
||
##### Watchlist / email-notification sign-up relationship
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- `selectEmailNotifications(...)` creates/updates a watched-case record with `pinswg_emailnotifications: true`
|
||
- sign-in is prompted when a user attempts the action without the required authenticated/contact context
|
||
- email notification state is therefore primarily represented first in watchlist CRM state, not in Notify state directly
|
||
|
||
##### Aggregation / batch notification bootstrap
|
||
|
||
- `pages/api/email/getall.js`
|
||
- fetches watchlist entries with expanded contact data
|
||
- gathers recent documents, SIP events, and representation consultation-period data per watched case
|
||
- groups by contact email
|
||
- builds EN/CY Notify payloads
|
||
- sends outbound case-update emails in batch
|
||
|
||
- `pages/api/email/getdocuments.js`
|
||
- loads recent published documents for an incident
|
||
- enriches results with secure document download links
|
||
|
||
- `pages/api/email/getevents.js`
|
||
- loads SIP record and related SIP events for an incident
|
||
|
||
- `pages/api/email/getmailinglist.js`
|
||
- returns flattened watchlist/contact email data for notification audiences
|
||
|
||
- `pages/api/email/getcaseref.js`
|
||
- returns watchlist entries including watched-case reference and appeal-type context
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary ownership layers
|
||
|
||
- **Notify payload state is mostly ephemeral**
|
||
- built at send time in route or caller logic
|
||
- not owned by a long-lived Redux slice
|
||
|
||
- **CRM watchlist state**
|
||
- is the strongest durable owner for business-notification intent
|
||
- specifically owns whether `pinswg_emailnotifications` is enabled for a watched case
|
||
|
||
- `store/watchedCases/reducer.js`
|
||
- owns client-visible watchlist and email-notification status after read/refresh
|
||
- supports search/case/myportal UI refresh after watched-case changes
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- provides email address and CRM contact identity used by completion email callers and watchlist email-notification mutations
|
||
|
||
##### Ownership note
|
||
|
||
- email sending itself is not the source of truth.
|
||
The durable ownership model differs by sub-journey:
|
||
- **auth sign-in email** -> NextAuth-driven
|
||
- **completion email** -> completion/orchestration caller-driven
|
||
- **watchlist updates** -> CRM watchlist state-driven
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/notifyDirectService.js`
|
||
- `sendEmail(...)`
|
||
|
||
- `actions/services/notifyService.js`
|
||
- re-exports `sendEmail(...)`
|
||
|
||
- adjacent service callers:
|
||
- `actions/services/accountDirectService.js`
|
||
- `getPreferredLanguage(...)`
|
||
- `actions/services/portalDirectService.js`
|
||
- completion-message related orchestration routes adjacent to email lifecycle
|
||
|
||
##### Journey role of services
|
||
|
||
- `sendEmail(...)`
|
||
- is the main thin abstraction for direct Notify sends from UI-side completion flows
|
||
- account service helpers
|
||
- supply preferred-language or contact context used to shape mail behaviour
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/email/notify.js`
|
||
- `pages/api/email/getall.js`
|
||
- `pages/api/email/getdocuments.js`
|
||
- `pages/api/email/getevents.js`
|
||
- `pages/api/email/getmailinglist.js`
|
||
- `pages/api/email/getcaseref.js`
|
||
- auth-support email path also embedded in:
|
||
- `pages/api/auth/[...nextauth].js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `notify.js`
|
||
- **direct Notify send route**
|
||
- thin send-focused route with small preferred-language exception for new-case-reference mail
|
||
|
||
- `getall.js`
|
||
- **notification orchestration / batch route**
|
||
- aggregates CRM watchlist, document, event, and consultation-period data before sending
|
||
|
||
- `getdocuments.js`
|
||
- **notification-support data route**
|
||
- document lookup and link-building for mail payload assembly
|
||
|
||
- `getevents.js`
|
||
- **notification-support data route**
|
||
- SIP-event lookup for mail payload assembly
|
||
|
||
- `getmailinglist.js`
|
||
- **notification-support audience route**
|
||
- mailing list flattening over watchlist/contact data
|
||
|
||
- `getcaseref.js`
|
||
- **notification-support audience/context route**
|
||
- watched-case reference and appeal-type lookup for notification context
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **GOV.UK Notify**
|
||
- direct outbound email send boundary for all reviewed notification types
|
||
|
||
- **CRM via Azure Relay**
|
||
- used for:
|
||
- preferred-language lookup
|
||
- watchlist audience retrieval
|
||
- watched-case reference lookup
|
||
- recent documents lookup
|
||
- SIP events lookup
|
||
- representation consultation-period lookup
|
||
|
||
- **NextAuth**
|
||
- not the main owner of business notifications
|
||
- does own the auth sign-in mail use case
|
||
|
||
- **Azure Storage**
|
||
- not directly part of reviewed email routes
|
||
- adjacent completion journeys may touch storage/finalisation before or around completion email send, but email routes themselves remain Notify/CRM-oriented here
|
||
|
||
- **Local-only processing**
|
||
- template selection
|
||
- payload formatting
|
||
- bilingual section-building
|
||
- secure link concatenation for document mail content
|
||
|
||
#### Ownership / Identity Model
|
||
|
||
```text
|
||
Account/contact identity
|
||
→ email address + preferred language
|
||
→ business event or watchlist state
|
||
→ Notify payload build
|
||
→ GOV.UK Notify send
|
||
```
|
||
|
||
For watchlist-driven notifications specifically:
|
||
|
||
```text
|
||
CRM watched case
|
||
→ pinswg_emailnotifications == true
|
||
→ contact email + preferred language
|
||
→ case-linked document/event/reps aggregation
|
||
→ batch Notify send
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
##### Direct completion-style send
|
||
|
||
User completes journey
|
||
→ completion component (`newappeal` or `representation`)
|
||
→ `actions/services/notifyService.sendEmail(...)`
|
||
→ `pages/api/email/notify.js`
|
||
→ GOV.UK Notify
|
||
|
||
##### Watchlist-driven batch updates
|
||
|
||
Scheduler / triggered route call
|
||
→ `pages/api/email/getall.js`
|
||
→ watchlist/contact fetch
|
||
→ per-case documents/events/reps aggregation
|
||
→ bilingual payload shaping
|
||
→ GOV.UK Notify batch send
|
||
|
||
##### Auth-support sign-in email
|
||
|
||
User enters email
|
||
→ `pages/api/auth/[...nextauth].js`
|
||
→ localized verification URL build
|
||
→ GOV.UK Notify sign-in email
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/api/email/notify.js`
|
||
- `pages/api/email/getall.js`
|
||
- `pages/api/email/getdocuments.js`
|
||
- `pages/api/email/getevents.js`
|
||
- `pages/api/email/getmailinglist.js`
|
||
- `pages/api/email/getcaseref.js`
|
||
- `actions/services/notifyDirectService.js`
|
||
- `actions/services/notifyService.js`
|
||
- `components/newappeal/complete.js`
|
||
- `components/case/representation/representationComplete.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- `store/watchedCases/reducer.js`
|
||
- `pages/api/documents/download/[id].js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- template selection and EN/CY parity
|
||
- business-event timing versus email send timing
|
||
- watchlist/contact grouping assumptions in batch notification route
|
||
- document-link and case-link generation inside email payloads
|
||
- mixed responsibility in `getall.js` across audience retrieval, content aggregation, and send behaviour
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- user-facing communications with visible side effects
|
||
- includes both simple sends and orchestration-heavy aggregation
|
||
- depends on CRM audience/content correctness and bilingual template continuity
|
||
|
||
### Ownership / Identity Model
|
||
|
||
#### Authentication / Sign-In
|
||
|
||
```text
|
||
NextAuth session
|
||
→ session.user.email
|
||
→ getPortalLogin(email)
|
||
→ CRM contact or registration redirect
|
||
→ pinsUser cookie
|
||
→ portal entry continuity
|
||
```
|
||
|
||
#### Notifications / Email
|
||
|
||
```text
|
||
CRM contact / account email
|
||
→ preferred language + journey event or watchlist state
|
||
→ Notify payload
|
||
→ GOV.UK Notify
|
||
```
|
||
|
||
#### Combined interpretation
|
||
|
||
- Authentication owns the transition from:
|
||
- anonymous or pre-session identity
|
||
- into session identity
|
||
- and then into CRM-backed portal continuity
|
||
- Notifications own the transition from:
|
||
- CRM/account/contact context or journey completion context
|
||
- into outbound user communication
|
||
- The overlap is strongest where sign-in email and preferred-language resolution use the same CRM contact/email model that later business notifications also reuse.
|
||
|
||
### Future API Grouping Assessment
|
||
|
||
This section is a **future grouping assessment** only.
|
||
|
||
It is **not an implementation recommendation**.
|
||
|
||
| Current route | Journey owner | Integration touched | Future grouping candidate | Migration caution |
|
||
| ------------------------------------------------ | ---------------------------------------------- | -------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `pages/api/auth/[...nextauth].js` | authentication / sign-in platform | NextAuth + Notify + CRM lookup support | should remain platform-level | core auth/session boundary with callback, redirect, verify-request, and sign-in email behaviour; journey ownership is auth but boundary is platform-critical |
|
||
| `pages/api/auth/resolve-locale.js` | authentication / sign-in support | CRM Relay + cookie/request locale | candidate for `api/auth` or `api/shared` | locale helper is auth-adjacent but also behaves like a small shared support route; migration caution because it joins locale and CRM lookup concerns |
|
||
| `pages/api/email/notify.js` | notifications / email direct send | GOV.UK Notify + optional CRM language | candidate for `api/notifications` | mostly send-focused, but contains preferred-language exception for `PEDW-NEW-CASEREF`; migration caution because it is not purely transport-only |
|
||
| `pages/api/email/getall.js` | notifications / email batch orchestration | GOV.UK Notify + CRM Relay | candidate for `api/notifications` | broad orchestration route with audience lookup, aggregation, payload building, and send side effects; migration caution due to mixed responsibilities |
|
||
| `pages/api/email/getdocuments.js` | notifications / email support | CRM Relay + secure document-link logic | candidate for `api/notifications` or `api/shared` | supports email aggregation but overlaps with wider document-retrieval concepts; migration caution because route has support-role rather than standalone user journey |
|
||
| `pages/api/email/getevents.js` | notifications / email support | CRM Relay | candidate for `api/notifications` or `api/shared` | support route for email payload assembly; migration caution because event data may also be meaningful outside notification use |
|
||
| `pages/api/email/getmailinglist.js` | notifications / email audience support | CRM Relay | candidate for `api/notifications` | strong notification-audience fit, but still reflects watchlist CRM ownership rather than a standalone notification-owned record source |
|
||
| `pages/api/email/getcaseref.js` | notifications / email audience/context support | CRM Relay | candidate for `api/notifications` or `api/shared` | context-support route for watched-case notification assembly; migration caution because ownership overlaps with watched-case domain context |
|
||
| `pages/api/endpoint/getpreferredlanguage_api.js` | auth/email shared support | CRM Relay | candidate for `api/shared` | supports both sign-in locale resolution and mail-template decisions; journey ownership is shared, so migration caution is primarily boundary ambiguity |
|
||
| `pages/api/endpoint/getportallogin_api.js` | authentication / portal bootstrap | CRM Relay + signed request integrity | candidate for `api/auth` or `api/account` | bootstrap identity lookup is used by sign-in continuity and registration branching; migration caution because journey ownership spans auth and account edges |
|
||
|
||
#### Classification notes
|
||
|
||
- **candidate for `api/auth`**
|
||
- routes whose clearest journey ownership is sign-in, callback, locale resolution, or portal-auth bootstrap
|
||
- **candidate for `api/notifications`**
|
||
- routes whose clearest journey ownership is outbound mail send, audience assembly, or mail payload orchestration
|
||
- **candidate for `api/account`**
|
||
- routes whose visible ownership is closer to account/bootstrap identity than to session mechanics alone
|
||
- **candidate for `api/shared`**
|
||
- support routes reused across auth and notification concerns
|
||
- **should remain platform-level**
|
||
- routes whose boundary is fundamentally platform/auth infrastructure rather than a narrow journey slice
|
||
- **unclear / historical**
|
||
- not the dominant classification for the sampled auth/email routes, but still relevant when route ownership is mixed or legacy-shaped
|
||
|
||
### Auth vs Notification Comparison
|
||
|
||
#### Where they are independent
|
||
|
||
- Authentication / Sign-In owns:
|
||
- sign-in form entry
|
||
- verify-request page
|
||
- callback handling
|
||
- session creation
|
||
- redirect logic
|
||
- logout/reset continuity
|
||
- Notifications / Email owns:
|
||
- direct Notify sends
|
||
- completion emails
|
||
- watchlist update emails
|
||
- aggregation of documents/events/reps into outbound email content
|
||
|
||
#### Where they overlap
|
||
|
||
- both use email address as a key continuity field
|
||
- both use CRM preferred-language/contact lookup support
|
||
- both rely on GOV.UK Notify for actual outbound mail delivery in relevant sub-flows
|
||
- both have EN/CY template or locale-sensitive behaviour
|
||
|
||
#### Where Notify is used as an auth-support integration
|
||
|
||
- passwordless verification email in `pages/api/auth/[...nextauth].js`
|
||
- localized sign-in-link delivery based on CRM preferred language or request locale fallback
|
||
|
||
#### Where Notify is used as a business-notification integration
|
||
|
||
- appeal completion emails from `components/newappeal/complete.js`
|
||
- representation completion emails from `components/case/representation/representationComplete.js`
|
||
- watchlist / case-update batch sends in `pages/api/email/getall.js`
|
||
|
||
#### Practical maintainer distinction
|
||
|
||
- auth email is **identity-entry support**
|
||
- notification email is **business-event communication**
|
||
|
||
### Architectural Flows
|
||
|
||
#### Authentication / Sign-In
|
||
|
||
```text
|
||
User
|
||
→ /auth/signin
|
||
→ resolve-locale(email, locale)
|
||
→ /api/auth/signin/email
|
||
→ NextAuth verification flow
|
||
→ GOV.UK Notify sign-in email
|
||
→ callback/email verification
|
||
→ session created
|
||
→ homepage bootstrap
|
||
→ getPortalLogin(email)
|
||
→ /myportal or /account/register
|
||
```
|
||
|
||
#### Notifications / Email
|
||
|
||
```text
|
||
Business event or watchlist state
|
||
→ direct send route or aggregation route
|
||
→ optional CRM/document/event enrichment
|
||
→ EN/CY template selection
|
||
→ GOV.UK Notify send
|
||
→ user receives portal communication
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Authentication / Sign-In
|
||
|
||
- start with:
|
||
- `pages/auth/signin.js`
|
||
- `pages/auth/verify-request.js`
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- `pages/index.js`
|
||
- `lib/auth/sessionClient.js`
|
||
- `pages/api/endpoint/{getportallogin_api,getpreferredlanguage_api}.js`
|
||
- `actions/services/accountDirectService.js`
|
||
|
||
#### Notifications / Email
|
||
|
||
- start with:
|
||
- `pages/api/email/{notify,getall,getdocuments,getevents,getmailinglist,getcaseref}.js`
|
||
- `actions/services/{notifyDirectService,notifyService}.js`
|
||
- `components/newappeal/complete.js`
|
||
- `components/case/representation/representationComplete.js`
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- `pages/api/auth/[...nextauth].js` for auth-support mail continuity
|
||
|
||
### Risk Classification
|
||
|
||
- Authentication / Sign-In: **Very High**
|
||
- Notifications / Email: **High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 5
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/portal-api-platform-assessment.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Guardrails/context discipline:
|
||
|
||
- `.clinerules/refactor-branch-rules.md`
|
||
- `GUARDRAILS.md`
|
||
|
||
Journey pages / components / helpers:
|
||
|
||
- `pages/auth/signin.js`
|
||
- `pages/auth/verify-request.js`
|
||
- `pages/index.js`
|
||
- `pages/logout.js`
|
||
- `pages/account/register.js`
|
||
- `components/header.js`
|
||
- `components/myportal/servicebanner.js`
|
||
- `components/newappeal/complete.js`
|
||
- `components/case/representation/representationComplete.js`
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- `lib/auth/sessionClient.js`
|
||
- `actions/services/accountDirectService.js`
|
||
- `actions/services/notifyDirectService.js`
|
||
- `actions/services/notifyService.js`
|
||
|
||
API files:
|
||
|
||
- `pages/api/auth/[...nextauth].js`
|
||
- `pages/api/auth/resolve-locale.js`
|
||
- `pages/api/email/notify.js`
|
||
- `pages/api/email/getall.js`
|
||
- `pages/api/email/getdocuments.js`
|
||
- `pages/api/email/getevents.js`
|
||
- `pages/api/email/getmailinglist.js`
|
||
- `pages/api/email/getcaseref.js`
|
||
- `pages/api/endpoint/getpreferredlanguage_api.js`
|
||
- `pages/api/endpoint/getportallogin_api.js`
|
||
|
||
#### Searches performed for Slice 5
|
||
|
||
- `pages`: `getServerSideProps|getSession\(|signIn\(|signOut\(|getCsrfToken\(|verify-request|nextauth|resolve-locale|logout`
|
||
- `actions/services`: `notify|getPortalLogin|getPreferredLanguage|send.*email|create.*message`
|
||
- `pages/api`: `NotifyClient|sendEmail|NextAuth|EmailProvider|verification|callback|session|getall|getdocuments|getevents|getmailinglist|getcaseref`
|
||
- `lib`: `auth|session|locale|preferredLanguage|getPortalLogin`
|
||
- `components`: `logout|sign in|verify|email|notify|preferred language`
|
||
|
||
#### Limitations for Slice 5
|
||
|
||
- This slice was intentionally limited to auth/sign-in and notifications/email only.
|
||
- It did not reopen the completed Authorization Architecture Assessment.
|
||
- It did not reassess exploitability, security posture, or ownership risk.
|
||
- It traced submission, representation, registration, watchlist, and portal pages only where needed to explain auth/email touchpoints.
|
||
- The future grouping section is a classification exercise only and not an implementation recommendation.
|
||
- No runtime execution, mail send, or sign-in flow testing was performed.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. Authentication / Sign-In and Notifications / Email are both cross-cutting, so their practical ownership spans page, API, integration, and bootstrap boundaries rather than one narrow folder.
|
||
2. `pages/api/auth/[...nextauth].js` includes both platform-auth behaviour and Notify-backed email behaviour; its future grouping candidate should therefore be read as architectural classification only.
|
||
3. `pages/api/email/getall.js` is materially more orchestration-heavy than `pages/api/email/notify.js`, so “notifications/email” is not one uniform route shape.
|
||
4. `getpreferredlanguage_api.js` and `getportallogin_api.js` support both auth and email journeys; their future grouping candidates are shared/auth/account classifications only, not an implementation recommendation.
|
||
5. Watchlist email behaviour is partly owned by CRM watchlist state (`pinswg_emailnotifications`) rather than by the Notify send layer alone.
|
||
|
||
### Validation Performed
|
||
|
||
- Confirmed the required Slice 5 context files were read.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Verified the next journey-map section followed the same maintainability format as prior slices.
|
||
- Traced auth flow from sign-in page through locale resolution, NextAuth callback/session handling, homepage bootstrap, and registration redirect continuity.
|
||
- Traced email flow across direct Notify sends, auth sign-in email, completion emails, and watchlist/batch aggregation routes.
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Watchlist / Subscriptions and Unsubscribe Flows**
|
||
|
||
This would extend the journey map into the cross-cutting subscription lifecycle that connects case pages, search results, my portal state, CRM watchlist records, email-notification intent, and unsubscribe routes without widening into implementation work.
|
||
|
||
---
|
||
|
||
## Slice 6 — Watchlist / Subscriptions and Unsubscribe / Watchlist Removal
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- The watchlist/subscription architecture is a cross-cutting portal support journey built around a visible CRM relationship model:
|
||
|
||
```text
|
||
CRM Contact
|
||
↔ Watched Case
|
||
```
|
||
|
||
- The same watched-case relationship supports several visible behaviours at once:
|
||
- case watching
|
||
- dashboard visibility
|
||
- search-results visibility
|
||
- case-summary visibility
|
||
- email-notification participation through `pinswg_emailnotifications`
|
||
- Watchlist creation and removal are not isolated to one page.
|
||
They are triggered from:
|
||
- search results
|
||
- case summary
|
||
- my portal top-three cards
|
||
- my portal view-all lists
|
||
- dedicated unsubscribe pages for email-only removal
|
||
- Dashboard watchlist viewing is not an independent data model.
|
||
It is a dashboard projection over watched-case CRM retrieval plus additional detail enrichment.
|
||
- Notification participation is visibly a property of the watched-case relationship rather than a separate subscription entity in the reviewed frontend code.
|
||
- This slice does **not** reopen the authorization assessment.
|
||
It documents only visible watchlist/subscription architecture and ownership behaviour.
|
||
|
||
### Watchlist Creation Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a signed-in portal user to mark a case as watched so that it appears in their portal context and can later participate in email-notification flows.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey is the clearest entry into the visible watched-case relationship architecture because it shows how PEDW creates or updates a CRM relationship between:
|
||
- portal contact
|
||
- watched case
|
||
- optional email-notification participation
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/search/addresssearchresults.js`
|
||
- `components/search/dnssearchresults.js`
|
||
- `components/case/summary.js`
|
||
- adjacent authenticated search routes that preload watched-case state for the above components:
|
||
- `pages/myportal/searchresults.js`
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Signed-in watched-case availability in portal search flows
|
||
|
||
- `pages/myportal/searchresults.js`
|
||
- resolves session and CRM contact identity
|
||
- loads watched cases via `getWatchedCases(loggedInUser)`
|
||
- derives `watchedCasesDetails` via `getDetails(...)`
|
||
- dispatches:
|
||
- `setWatchedCases(...)`
|
||
- `setWatchedCasesDetails(...)`
|
||
- `setLoggedInUserId(...)`
|
||
- `setAccountDetails(...)`
|
||
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
- perform equivalent portal bootstrap for watched-case state before rendering search-style results pages in myportal context
|
||
|
||
##### Watch action branch in results and case summary
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- expose `selectWatchedCase(loggedInUser, incidentID, appealType)`
|
||
- construct watched-case relationship payload using:
|
||
- `pinswg_WatchedCase@odata.bind`
|
||
- `pinswg_Contact@odata.bind`
|
||
- `pinswg_appealcasetype`
|
||
- call `createWatchedCases(updateBody)`
|
||
- refresh watched-case state after mutation using:
|
||
- `getWatchedCasesProxy(...)`
|
||
- `getDetailsProxy(..., "myWatchedCases")`
|
||
|
||
##### Notification-enabled creation branch
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- expose `selectEmailNotifications(...)`
|
||
- create the same watched-case relationship with one additional visible field:
|
||
- `pinswg_emailnotifications: true`
|
||
- this means initial subscription signup is visibly implemented as watched-case upsert, not a separate notification-only create route
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/watchedCases/reducer.js`
|
||
- owns:
|
||
- `watchedCases`
|
||
- `watchedCasesDetails`
|
||
|
||
- `store/accountDetails/reducer.js`
|
||
- provides:
|
||
- `loggedinUserId`
|
||
- `accountDetails.contactid`
|
||
- `accountDetails.emailaddress1`
|
||
- these values are used to create the watched-case relationship and optional email-notification participation
|
||
|
||
- `store/currentView/reducer.js`
|
||
- participates in preserving origin/view context when navigating into watched cases or back into myportal list views
|
||
|
||
##### Ownership note
|
||
|
||
- creation-state ownership is therefore split between:
|
||
- contact identity in `accountDetails`
|
||
- watched-case list/read model in `watchedCases`
|
||
- view context in `currentView`
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `createWatchedCases(formValues)`
|
||
- `getWatchedCases(loggedInUserId)`
|
||
- `getWatchedCasesProxy(loggedInUserId)`
|
||
|
||
##### Journey role of services
|
||
|
||
- `createWatchedCases(...)`
|
||
- is the main visible watched-case upsert entry
|
||
- `getWatchedCases(...)` and `getWatchedCasesProxy(...)`
|
||
- are used immediately after mutation to refresh portal-visible state
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/createwatchedcases_api.js`
|
||
- adjacent read routes used immediately after create:
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getwatchedcasesproxy_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `createwatchedcases_api.js`
|
||
- **CRM relationship upsert / orchestration route**
|
||
- derives watched case id and contact id from odata bind payload
|
||
- checks for an existing relationship first
|
||
- patches an existing watchlist record or creates a new one
|
||
|
||
- `getwatchedcases_api.js`
|
||
- **CRM relationship read**
|
||
- retrieves watched cases by contact ownership
|
||
|
||
- `getwatchedcasesproxy_api.js`
|
||
- **CRM relationship read / proxy variant**
|
||
- returns a closely related watched-case read model for refresh and portal display support
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary watched-case relationship store
|
||
- handles relationship create/read/update behavior
|
||
|
||
- **NextAuth**
|
||
- not the route-local owner of watch creation itself
|
||
- but is the upstream identity root used to establish the CRM contact before watched-case actions become available
|
||
|
||
- **GOV.UK Notify**
|
||
- not directly touched during watch creation itself
|
||
- but `pinswg_emailnotifications` visibly links the created relationship into later notification participation
|
||
|
||
- **Local-only processing**
|
||
- JSONPath watched/unwatched state checks in components
|
||
- post-mutation refresh of Redux state
|
||
- conditional watch/watch-email button rendering
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
CRM Contact
|
||
→ watched-case payload bindings
|
||
→ createWatchedCases
|
||
→ CRM watchlist record exists or is created
|
||
→ watchedCases Redux refresh
|
||
```
|
||
|
||
Visible fields used to represent the relationship include:
|
||
|
||
- `pinswg_WatchedCase@odata.bind`
|
||
- `pinswg_Contact@odata.bind`
|
||
- `pinswg_appealcasetype`
|
||
- optionally `pinswg_emailnotifications`
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ search results or case summary watch action
|
||
→ `selectWatchedCase(...)` or `selectEmailNotifications(...)`
|
||
→ `actions/services/portalDirectService.createWatchedCases(...)`
|
||
→ `pages/api/endpoint/createwatchedcases_api.js`
|
||
→ CRM `pinswg_watchlists` create/patch
|
||
→ `getWatchedCasesProxy(...)` refresh
|
||
→ Redux `watchedCases` + `watchedCasesDetails`
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/search/addresssearchresults.js`
|
||
- `components/search/dnssearchresults.js`
|
||
- `components/case/summary.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/endpoint/createwatchedcases_api.js`
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getwatchedcasesproxy_api.js`
|
||
- `store/watchedCases/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/myportal/searchresults.js`
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
- `store/accountDetails/reducer.js`
|
||
- `store/currentView/reducer.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- relationship upsert behaviour in `createwatchedcases_api.js`
|
||
- immediate post-create refresh assumptions
|
||
- component-level JSONPath watched/not-watched checks
|
||
- dual use of the same create route for both watch and email-notification signup
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- cross-cutting relationship creation used from multiple entry points
|
||
- state refresh must stay aligned across search, case, and portal contexts
|
||
- the same relationship underpins later dashboard and notification behaviour
|
||
|
||
### Watchlist Viewing Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a portal user to see watched cases in their dashboard and related portal list views.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how watched-case CRM records are read, filtered, classified, enriched, and then displayed across myportal and adjacent signed-in search/case contexts.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/myportal/index.js`
|
||
- `components/myportal/watchedcases.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- adjacent portal search pages that preload watched cases for watch/unwatch controls:
|
||
- `pages/myportal/searchresults.js`
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Dashboard bootstrap
|
||
|
||
- `pages/myportal/index.js`
|
||
- resolves session and CRM contact identity
|
||
- loads watched cases via `getWatchedCases(loggedInUser)`
|
||
- classifies results with `splitWatchedCasesBySubmissionState(watchedCases.value)` into:
|
||
- `watchedCases`
|
||
- `submittedRepresentations`
|
||
- enriches watched cases with `getDetails(..., "myWatchedCases")`
|
||
- dispatches:
|
||
- `setWatchedCases(filteredWatchedCases)`
|
||
- `setWatchedCasesDetails(watchedCasesDetails)`
|
||
|
||
##### Watched-cases dashboard card
|
||
|
||
- `components/myportal/watchedcases.js`
|
||
- renders the watched-cases card
|
||
- delegates list/card rendering to `TopThree`
|
||
- sends users to `/myportal/viewall?key=watchedCases` with `setCurrentView({ viewName: "Watched Cases", viewKey: "watchedCases" })`
|
||
|
||
##### Portal view-all bootstrap
|
||
|
||
- `components/myportal/viewall.js`
|
||
- treats `currentViewKey === "watchedCases"` as one of the main list modes
|
||
- uses:
|
||
- `props.watchedCases.watchedCases`
|
||
- `props.watchedCases.watchedCasesDetails`
|
||
- sets watched cases into search/detail state when navigating deeper into a case from this list
|
||
|
||
##### Portal search viewing support
|
||
|
||
- `pages/myportal/searchresults.js`
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
- preload watched-case state to support portal-context watch/unwatch controls inside results pages
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/watchedCases/reducer.js`
|
||
- durable view-state owner for:
|
||
- watched-case read model
|
||
- watched-case details enrichment model
|
||
|
||
- `store/currentView/reducer.js`
|
||
- records whether the active dashboard/list context is:
|
||
- `watchedCases`
|
||
- preserves navigation back into view-all and case contexts
|
||
|
||
- `store/searchOutput/reducer.js`
|
||
- is temporarily reused by `viewall.js` when a watched-case item is opened via case-detail navigation
|
||
|
||
##### Ownership note
|
||
|
||
- watchlist viewing is not a separate standalone state store.
|
||
It is a combination of:
|
||
- watched-case list state
|
||
- watched-case detail enrichment
|
||
- dashboard/view context
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `getWatchedCases(...)`
|
||
- `getWatchedCasesProxy(...)`
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `getPortalModuleDetails(...)`
|
||
- used indirectly for watched-case detail enrichment
|
||
|
||
##### Supporting domain helper
|
||
|
||
- `lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js`
|
||
- separates plain watched cases from submitted representation-related records
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getwatchedcasesproxy_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `getwatchedcases_api.js`
|
||
- **CRM relationship read**
|
||
- filters `pinswg_watchlists` by `_pinswg_contact_value eq loggedInUserId`
|
||
- selects visible watchlist fields including:
|
||
- `pinswg_emailnotifications`
|
||
- `pinswg_watchlistid`
|
||
- `_pinswg_watchedcase_value`
|
||
- submission/representation-related fields
|
||
|
||
- `getwatchedcasesproxy_api.js`
|
||
- **CRM relationship read / proxy variant**
|
||
- similar relationship retrieval with a slightly different selected field set
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary watched-case retrieval boundary
|
||
|
||
- **NextAuth**
|
||
- upstream identity root used to determine which CRM contact’s watched cases are loaded
|
||
|
||
- **Local-only processing**
|
||
- classification of watched cases vs submitted representations
|
||
- sorting, detail enrichment, and view-all routing
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
CRM Contact
|
||
→ getWatchedCases(contactId)
|
||
→ CRM watchlist rows
|
||
→ splitWatchedCasesBySubmissionState
|
||
→ detail enrichment
|
||
→ dashboard / view-all projection
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ `pages/myportal/index.js`
|
||
→ `getWatchedCases(loggedInUser)`
|
||
→ `pages/api/endpoint/getwatchedcases_api.js`
|
||
→ CRM watchlist rows
|
||
→ `splitWatchedCasesBySubmissionState(...)`
|
||
→ `getPortalModuleDetails(...)` enrichment
|
||
→ Redux `watchedCases` + `watchedCasesDetails`
|
||
→ dashboard card / top-three / view-all
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/myportal/index.js`
|
||
- `components/myportal/watchedcases.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getwatchedcasesproxy_api.js`
|
||
- `store/watchedCases/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js`
|
||
- `pages/myportal/searchresults.js`
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
- `store/currentView/reducer.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- watched-case classification versus submitted-representation classification
|
||
- enrichment fan-out through portal module details
|
||
- reuse of watched-case state in view-all and case-detail navigation contexts
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- dashboard-critical signed-in journey
|
||
- watched-case state is reused in several components and contexts
|
||
- visible relationship between dashboard and watchlist ownership is strong and cross-cutting
|
||
|
||
### Watchlist Removal Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a user to stop watching a case, removing it from portal watchlist views and related watch-state controls.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how removal uses the same watched-case CRM relationship record as creation/viewing, and how portal state is refreshed after deletion.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/search/addresssearchresults.js`
|
||
- `components/search/dnssearchresults.js`
|
||
- `components/case/summary.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Removal in search and case contexts
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- use `deleteItem(caseID, "watchedCases")`
|
||
- call `deleteWatchedCases(caseID)`
|
||
- refresh watched cases via `getWatchedCasesProxy(...)`
|
||
- rehydrate `watchedCases` and `watchedCasesDetails`
|
||
|
||
##### Removal in dashboard card/view-all contexts
|
||
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- also use `deleteWatchedCases(...)`
|
||
- refresh and reclassify watched cases through:
|
||
- `getWatchedCasesProxy(...)`
|
||
- `splitWatchedCasesBySubmissionState(...)`
|
||
- `getDetailsProxy(..., "myWatchedCases")`
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/watchedCases/reducer.js`
|
||
- is rewritten after every successful remove flow
|
||
|
||
- `store/currentView/reducer.js`
|
||
- retains watched-cases list context during myportal list refreshes
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/portalDirectService.js`
|
||
- `deleteWatchedCases(watchedCaseID)`
|
||
- `getWatchedCasesProxy(loggedInUserId)`
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/deletewatchedcases_api.js`
|
||
- `pages/api/endpoint/deletewatchedcasesproxy_api.js`
|
||
|
||
##### Route-family classification
|
||
|
||
- `deletewatchedcases_api.js`
|
||
- **CRM relationship delete**
|
||
- deletes a `pinswg_watchlists(<watchedCaseID>)` record
|
||
|
||
- `deletewatchedcasesproxy_api.js`
|
||
- **proxy delete wrapper**
|
||
- forwards watched-case deletion through local endpoint routing
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary delete boundary for watched-case relationship removal
|
||
|
||
- **NextAuth / cached CRM identity context**
|
||
- upstream source of the watched-case ids exposed to portal UI flows
|
||
|
||
- **Local-only processing**
|
||
- list refresh
|
||
- classification refresh
|
||
- removal confirmation prompts
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Watched-case record id
|
||
→ deleteWatchedCases(watchedCaseID)
|
||
→ CRM watchlist record delete
|
||
→ watchedCases Redux refresh
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ unwatch action in search/case/dashboard/view-all
|
||
→ `actions/services/portalDirectService.deleteWatchedCases(...)`
|
||
→ `pages/api/endpoint/deletewatchedcases_api.js`
|
||
→ CRM watchlist record delete
|
||
→ `getWatchedCasesProxy(...)`
|
||
→ refreshed Redux watched-case state
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/endpoint/deletewatchedcases_api.js`
|
||
- `pages/api/endpoint/deletewatchedcasesproxy_api.js`
|
||
- `store/watchedCases/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/endpoint/getwatchedcasesproxy_api.js`
|
||
- `lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js`
|
||
- `store/currentView/reducer.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- refresh behaviour after delete across multiple UI surfaces
|
||
- reuse of watched-case ids between UI and delete route
|
||
- view-all state continuity after record removal
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- removal is available from multiple user-facing surfaces
|
||
- stale state or refresh drift can break dashboard/search/case consistency
|
||
- same relationship powers watch visibility and notification participation
|
||
|
||
### CRM Relationship Ownership Model
|
||
|
||
#### Visible relationship model
|
||
|
||
```text
|
||
CRM Contact
|
||
↔ Watched Case
|
||
```
|
||
|
||
#### Relationship entities used
|
||
|
||
- visible relationship entity:
|
||
- `pinswg_watchlists`
|
||
- visible linked fields include:
|
||
- `pinswg_watchlistid`
|
||
- `_pinswg_contact_value`
|
||
- `_pinswg_watchedcase_value`
|
||
- `pinswg_emailnotifications`
|
||
- `pinswg_appealcasetype`
|
||
- `pinswg_representationsubmitted`
|
||
- `pinswg_representationtype`
|
||
|
||
#### Retrieval pattern
|
||
|
||
- read by CRM contact ownership:
|
||
|
||
```text
|
||
pinswg_watchlists
|
||
→ filter _pinswg_contact_value eq loggedInUserId
|
||
→ expand pinswg_WatchedCase
|
||
→ flatten watched-case details for portal use
|
||
```
|
||
|
||
#### Creation pattern
|
||
|
||
- create/upsert path in `createwatchedcases_api.js`:
|
||
|
||
```text
|
||
payload contains pinswg_WatchedCase@odata.bind + pinswg_Contact@odata.bind
|
||
→ extract incidentId/contactId
|
||
→ lookup existing relationship in pinswg_watchlists
|
||
→ patch existing record or post new record
|
||
```
|
||
|
||
#### Deletion pattern
|
||
|
||
- delete by relationship record id:
|
||
|
||
```text
|
||
watchedCaseID
|
||
→ pinswg_watchlists(watchedCaseID)
|
||
→ CRM delete
|
||
```
|
||
|
||
#### Ownership interpretation
|
||
|
||
- the visible durable owner is not a separate portal subscription table in frontend state.
|
||
- instead the CRM watchlist relationship record is the main persistent ownership unit joining:
|
||
- contact
|
||
- case
|
||
- notification participation flag
|
||
|
||
### Future API Grouping Assessment
|
||
|
||
This section is a **future grouping assessment** only.
|
||
|
||
It is **not an implementation recommendation**.
|
||
|
||
| Current route | Journey owner | Integration touched | Future grouping candidate | Migration caution |
|
||
| --------------------------------------------------- | ---------------------------------------- | ----------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||
| `pages/api/endpoint/getwatchedcases_api.js` | watchlist viewing | CRM Relay | candidate for `api/watchlist` | core read model for dashboard and portal watch visibility; migration caution because current payload is reused across several UI contexts |
|
||
| `pages/api/endpoint/getwatchedcasesproxy_api.js` | watchlist viewing / refresh support | CRM Relay | candidate for `api/watchlist` or `api/shared` | proxy variant is tightly coupled to refresh behaviour and historical route usage; migration caution because caller expectations may differ |
|
||
| `pages/api/endpoint/createwatchedcases_api.js` | watchlist creation / subscription upsert | CRM Relay | candidate for `api/watchlist` | handles both create and patch behaviour plus notification-participation flag updates; migration caution because it is not create-only |
|
||
| `pages/api/endpoint/deletewatchedcases_api.js` | watchlist removal | CRM Relay | candidate for `api/watchlist` | central relationship delete route used from multiple surfaces; migration caution because many flows assume current watchedCaseID semantics |
|
||
| `pages/api/endpoint/deletewatchedcasesproxy_api.js` | watchlist removal proxy support | Local proxy + CRM Relay | candidate for `api/watchlist` or `api/shared` | wrapper route reflects historical forwarding boundary; migration caution because path and caller behaviour are support-shaped rather than journey-pure |
|
||
|
||
#### Classification notes
|
||
|
||
- **candidate for `api/watchlist`**
|
||
- routes whose clearest visible owner is watched-case relationship creation, retrieval, or deletion
|
||
- **candidate for `api/shared`**
|
||
- proxy/support variants whose behaviour is coupled to refresh or forwarding patterns rather than one pure journey step
|
||
|
||
### Watchlist vs Dashboard Comparison
|
||
|
||
#### Shared dependencies
|
||
|
||
- both depend on:
|
||
- `getPortalLogin(session.user.email)` bootstrap upstream
|
||
- watched-case CRM retrieval
|
||
- `getPortalModuleDetails(...)` detail enrichment
|
||
- `splitWatchedCasesBySubmissionState(...)` when dashboard classification is involved
|
||
|
||
#### Shared state
|
||
|
||
- both use:
|
||
- `store/watchedCases.reducer.js`
|
||
- `store/currentView.reducer.js`
|
||
- `store/accountDetails.reducer.js`
|
||
|
||
#### Shared APIs
|
||
|
||
- both directly or indirectly depend on:
|
||
- `getwatchedcases_api.js`
|
||
- `getwatchedcasesproxy_api.js`
|
||
- `createwatchedcases_api.js`
|
||
- `deletewatchedcases_api.js`
|
||
|
||
#### Ownership relationship
|
||
|
||
- dashboard is a projection/consumer of watched-case ownership, not a separate watchlist owner
|
||
- watchlist journey owns the CRM relationship lifecycle
|
||
- dashboard journey owns the signed-in card/list presentation of that relationship
|
||
|
||
### Architectural Flows
|
||
|
||
#### Watchlist Creation
|
||
|
||
```text
|
||
User
|
||
→ search results / case summary watch action
|
||
→ createWatchedCases(payload)
|
||
→ createwatchedcases_api
|
||
→ CRM watchlist create/patch
|
||
→ getWatchedCasesProxy
|
||
→ watchedCases Redux refresh
|
||
```
|
||
|
||
#### Watchlist Viewing
|
||
|
||
```text
|
||
User
|
||
→ myportal bootstrap
|
||
→ getWatchedCases(contactId)
|
||
→ CRM watchlist retrieval
|
||
→ splitWatchedCasesBySubmissionState
|
||
→ detail enrichment
|
||
→ dashboard card / top-three / view-all
|
||
```
|
||
|
||
#### Watchlist Removal
|
||
|
||
```text
|
||
User
|
||
→ unwatch action or unsubscribe path
|
||
→ deleteWatchedCases(watchedCaseID)
|
||
→ CRM watchlist delete
|
||
→ watchedCases refresh or unsubscribe confirmation page
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Watchlist Creation
|
||
|
||
- start with:
|
||
- `components/search/searchresults.js`
|
||
- `components/search/addresssearchresults.js`
|
||
- `components/search/dnssearchresults.js`
|
||
- `components/case/summary.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/endpoint/{createwatchedcases_api,getwatchedcases_api,getwatchedcasesproxy_api}.js`
|
||
- `store/watchedCases/reducer.js`
|
||
|
||
#### Watchlist Viewing
|
||
|
||
- start with:
|
||
- `pages/myportal/index.js`
|
||
- `components/myportal/watchedcases.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- `pages/api/endpoint/{getwatchedcases_api,getwatchedcasesproxy_api}.js`
|
||
- `lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js`
|
||
- `store/watchedCases/reducer.js`
|
||
|
||
#### Watchlist Removal
|
||
|
||
- start with:
|
||
- `components/search/searchresults.js`
|
||
- `components/case/summary.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `pages/api/endpoint/{deletewatchedcases_api,deletewatchedcasesproxy_api}.js`
|
||
- `pages/unsubscribe/[watchlistid].js`
|
||
- `pages/unsubscribeall/[watchlistid].js`
|
||
|
||
### Risk Classification
|
||
|
||
- Watchlist Creation: **High**
|
||
- Watchlist Viewing: **High**
|
||
- Watchlist Removal: **High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 6
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/portal-api-security-boundary-assessment.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Journey pages / components / services / state:
|
||
|
||
- `pages/myportal/index.js`
|
||
- `pages/myportal/searchresults.js`
|
||
- `pages/myportal/addresssearchresults.js`
|
||
- `pages/myportal/advancedsearchresults.js`
|
||
- `pages/unsubscribe/[watchlistid].js`
|
||
- `pages/unsubscribeall/[watchlistid].js`
|
||
- `components/search/searchresults.js`
|
||
- `components/search/addresssearchresults.js`
|
||
- `components/search/dnssearchresults.js`
|
||
- `components/case/summary.js`
|
||
- `components/myportal/watchedcases.js`
|
||
- `components/myportal/topthree.js`
|
||
- `components/myportal/viewall.js`
|
||
- `actions/services/portalDirectService.js`
|
||
- `store/watchedCases/reducer.js`
|
||
- `store/watchedCases/action.js`
|
||
|
||
API files:
|
||
|
||
- `pages/api/endpoint/getwatchedcases_api.js`
|
||
- `pages/api/endpoint/getwatchedcasesproxy_api.js`
|
||
- `pages/api/endpoint/createwatchedcases_api.js`
|
||
- `pages/api/endpoint/deletewatchedcases_api.js`
|
||
- `pages/api/endpoint/deletewatchedcasesproxy_api.js`
|
||
- `pages/api/email/getall.js`
|
||
- `pages/api/email/getmailinglist.js`
|
||
- `pages/api/email/getcaseref.js`
|
||
|
||
#### Searches performed for Slice 6
|
||
|
||
- `pages`: `unsubscribe|watchlist|watchedcases|getWatchedCases|createWatchedCases|deleteWatchedCases`
|
||
- `components`: `selectWatchedCase|selectEmailNotifications|deleteItem|watchedCases|unsubscribe|send-email-notifications|stop-sending-email-notifications`
|
||
- `actions/services`: `getWatchedCases|createWatchedCases|deleteWatchedCases|getWatchedCasesProxy|watchlist`
|
||
- `pages/api`: `getwatchedcases|createwatchedcases|deletewatchedcases|unsubscribe|watchlist|pinswg_emailnotifications`
|
||
- `store`: `watchedCases|watchedCasesDetails|setWatchedCases|setWatchedCasesDetails|setCurrentView`
|
||
|
||
#### Limitations for Slice 6
|
||
|
||
- This slice was intentionally limited to watched-case/subscription and unsubscribe/removal architecture only.
|
||
- It did not reopen the security assessment beyond reusing already-established ownership context.
|
||
- It did not speculate beyond visible fields, flows, and routes in the codebase.
|
||
- It did not propose route redesign, state redesign, or ownership redesign.
|
||
- No runtime execution or unsubscribe-flow testing was performed.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. The watched-case relationship is used for both watch visibility and notification participation, so changes in one part of the journey can affect multiple user-visible surfaces.
|
||
2. `createwatchedcases_api.js` behaves as an upsert route rather than a simple create route, which is important for maintainers tracing watch vs email-subscription behaviour.
|
||
3. Dashboard watchlist displays are projections over watched-case CRM data and portal detail enrichment rather than a separate owned dashboard record set.
|
||
4. Dedicated unsubscribe pages implement removal through direct CRM watchlist queries and deletes rather than the same portal service helpers used in signed-in UI flows.
|
||
5. Proxy and non-proxy watched-case routes coexist, so journey ownership is clearer than folder ownership.
|
||
|
||
### Validation Performed
|
||
|
||
- Confirmed the required Slice 6 context files were read.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Reused the established watched-cases route family context from the API route map and ownership assessment without reopening exploitability analysis.
|
||
- Traced creation, viewing, removal, unsubscribe, and notification-participation touchpoints across pages, components, services, store, and API handlers.
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Documents / Published Document Retrieval and Download**
|
||
|
||
This would extend the journey map into the public-and-portal document access lifecycle that connects case/search document visibility, document metadata retrieval, download routing, and hash-link usage without widening into implementation work.
|
||
|
||
---
|
||
|
||
## Slice 7 — Published Document Discovery and Published Document Retrieval / Download
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- The visible published-document architecture is split into two linked but distinct maintainability shapes:
|
||
- **Published Document Discovery** is primarily a case-detail presentation journey backed by document metadata reads from the search/document endpoint family.
|
||
- **Published Document Retrieval / Download** is a dedicated download-proxy journey backed by a separate document-delivery route under `pages/api/documents/download/[id].js`.
|
||
- The strongest visible entry path is:
|
||
- public search result
|
||
- case detail navigation
|
||
- case documents panel
|
||
- document metadata retrieval
|
||
- per-document hash-link download
|
||
- The clearest visible metadata source is CRM document data queried through the relay-backed `pinswg_documents` family.
|
||
- The clearest visible delivery mechanism is:
|
||
- metadata route generates `pinswg_hashlink`
|
||
- browser fetches `/api/documents/download/[id]?hash=...`
|
||
- download proxy streams the relay-backed binary response to the browser
|
||
- Document history routes are present and classified in the API layer, but they were not surfaced by the reviewed case-detail UI path in this slice.
|
||
|
||
### Published Document Discovery Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a user to discover published case documents, review document names, document type labels, and publish dates, and navigate from a case view into downloadable published records.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how PEDW presents published-document metadata on case detail pages, including filter, sort, pagination, and document-type grouping behaviour without directly exposing CRM or relay details in the UI layer.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `components/search/searchresults.js`
|
||
- `pages/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/documents.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Search-to-case transition
|
||
|
||
- `components/search/searchresults.js`
|
||
- sets `currentReference` before navigating to the case route
|
||
- establishes the visible search-to-document navigation handoff through the case journey rather than a dedicated document page
|
||
|
||
##### Case page loader
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- bootstraps the case route via `getBasicSearch(developmentQuery)`
|
||
- expands case details via `getSearchDetails(searchResultsObj)`
|
||
- passes document-related runtime flags to the case page:
|
||
- `docsOffline`
|
||
- `showFilteredDocs`
|
||
- does not SSR-hydrate document metadata itself
|
||
|
||
##### Document metadata bootstrap in case UI
|
||
|
||
- `components/case/documents.js`
|
||
- is the main visible document-discovery loader for the reviewed journey
|
||
- on mount / dependency changes, calls:
|
||
- `getSearchDocumentTypes(incidentid)`
|
||
- `getSearchDocumentDetails(incidentid)`
|
||
- `getSearchDocumentDetailsPaged(...)`
|
||
- populates Redux document state through `setDocumentDetails(...)`
|
||
- derives document-availability UI from `docsOffline` via `getDocLink(docsOffline)`
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/searchOutput/reducer.js`
|
||
- owns `documentDetailsObj`
|
||
- this is the primary visible read model for case-document presentation
|
||
|
||
- `store/currentView/reducer.js`
|
||
- owns `currentPage`
|
||
- participates in document pagination state continuity
|
||
|
||
##### Document-discovery state in component layer
|
||
|
||
- `components/case/documents.js`
|
||
- owns local UI state for:
|
||
- `selectedOption`
|
||
- `documentTypes`
|
||
- `selectedDocumentType`
|
||
- `checkedItems`
|
||
- `selectAll`
|
||
- `orderByState`
|
||
- `fieldSortState`
|
||
- loading and download-status overlays
|
||
|
||
##### Search-to-document continuity
|
||
|
||
- `currentView.caseReference`
|
||
- is set before case navigation in search results
|
||
- provides continuity from search discovery into case document discovery context
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/searchDirectService.js`
|
||
- `getSearchDocumentDetails(incidentID)`
|
||
- `getSearchDocumentTypes(incidentID)`
|
||
- `getSearchDocumentDetailsPaged(...)`
|
||
|
||
- `actions/services/searchService.js`
|
||
- thin re-export layer used by case document UI
|
||
|
||
##### Supporting UI helpers
|
||
|
||
- `components/utils/downloads.js`
|
||
- consumes generated document hash links for user-triggered downloads
|
||
|
||
- `components/utils/downloadmanager.js`
|
||
- provides queued download orchestration in the browser
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/getsearchdocumentdetails_api.js`
|
||
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
|
||
- `pages/api/endpoint/getsearchdocumentTypes_api.js`
|
||
- adjacent but not visibly surfaced in the reviewed UI path:
|
||
- `pages/api/endpoint/getsearchdocumenthistory_api.js`
|
||
- `pages/api/endpoint/getsearchdocumenthistorypaged_api.js`
|
||
|
||
##### Route-family characteristics
|
||
|
||
- `getsearchdocumentdetails_api.js`
|
||
- **CRM relay read with metadata shaping**
|
||
- filters for published-to-web documents tied to the case incident id
|
||
- normalises `pinswg_documentpublisheddate`
|
||
- adds `pinswg_hashlink` for downstream download use
|
||
|
||
- `getsearchdocumentdetailspaged_api.js`
|
||
- **CRM relay read with pagination, filter, sort, and hash-link shaping**
|
||
- supports:
|
||
- page number
|
||
- sort field
|
||
- sort direction
|
||
- record-count preference
|
||
- document-type filtering
|
||
|
||
- `getsearchdocumentTypes_api.js`
|
||
- **CRM relay read with grouping transform**
|
||
- returns grouped document-type buckets and counts for the case documents filter UI
|
||
|
||
- `getsearchdocumenthistory*_api.js`
|
||
- **CRM relay read for document history metadata**
|
||
- present in the route family, but not visibly consumed in the reviewed case-detail path
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary source of published-document metadata
|
||
- touched through the search/document endpoint family
|
||
|
||
- **Local-only processing**
|
||
- document-type grouping presentation
|
||
- filter state
|
||
- pagination state
|
||
- download queue state in the browser
|
||
- locale-based label translation in the UI
|
||
|
||
- **NextAuth**
|
||
- not required for the public case document discovery path reviewed here
|
||
|
||
- **Azure Storage**
|
||
- not part of this published-document discovery flow
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Case incident id
|
||
→ getSearchDocumentDetails / getSearchDocumentDetailsPaged
|
||
→ CRM published document metadata
|
||
→ documentDetailsObj Redux state
|
||
→ case documents presentation
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ `components/search/searchresults.js` case selection
|
||
→ `pages/case/[ticketnumber].js`
|
||
→ `components/case.js` / `components/case/summary.js`
|
||
→ `components/case/documents.js`
|
||
→ `searchService.getSearchDocumentTypes(...)` + `getSearchDocumentDetails(...)` / `getSearchDocumentDetailsPaged(...)`
|
||
→ `pages/api/endpoint/getsearchdocumentTypes_api.js` / `getsearchdocumentdetails*_api.js`
|
||
→ `relayGet(...)`
|
||
→ Azure Relay
|
||
→ Dynamics 365 CRM
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/documents.js`
|
||
- `actions/services/searchDirectService.js`
|
||
- `store/searchOutput/action.js`
|
||
- `store/searchOutput/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `components/search/searchresults.js`
|
||
- `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`
|
||
- `components/utils/downloads.js`
|
||
- `components/utils/downloadmanager.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- document metadata shape expected by `components/case/documents.js`
|
||
- generated `pinswg_hashlink` continuity between metadata and download
|
||
- filter and pagination assumptions tied to `@odata.count` and `@odata.nextLink`
|
||
- `docsOffline` flag behaviour because it changes whether download links are surfaced
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- public-facing document discovery behaviour
|
||
- metadata retrieval, UI filtering, and download-link generation are tightly coupled
|
||
- document presentation depends on multiple route variants rather than a single narrow loader
|
||
|
||
### Published Document Retrieval / Download Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a user to retrieve a published document file once a visible document link is selected.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows the dedicated binary-delivery path, where the frontend does not download directly from CRM metadata routes but instead uses a separate download proxy route fed by the metadata-generated hash link.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `components/case/documents.js`
|
||
- `components/utils/downloads.js`
|
||
- `pages/api/documents/download/[id].js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Download link enablement
|
||
|
||
- `components/case/documents.js`
|
||
- uses `ShowDocLinks = getDocLink(docsOffline)` to determine whether link/button download behaviour is available
|
||
- passes document records into `DocumentLink`
|
||
|
||
##### Browser-side download start
|
||
|
||
- `components/utils/downloads.js`
|
||
- receives `detailsObj.pinswg_hashlink`
|
||
- on click, fetches the hash-link URL
|
||
- reads stream data in the browser
|
||
- derives filename from `content-disposition` when available
|
||
- creates a blob URL and triggers an `<a>` download
|
||
- emits a `DownloadedFile` analytics event
|
||
|
||
##### Download queuing
|
||
|
||
- `components/utils/downloadmanager.js`
|
||
- manages queued download tasks
|
||
- limits concurrent downloads
|
||
- tracks per-document statuses:
|
||
- `idle`
|
||
- `queued`
|
||
- `downloading`
|
||
- `done`
|
||
- `failed`
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary ownership
|
||
|
||
- there is no dedicated Redux download slice in the reviewed path
|
||
- download state is owned locally in the component/helper layer:
|
||
- `useDownloadQueue(...)` status map
|
||
- local progress state in `DocumentLink`
|
||
|
||
##### Metadata dependency
|
||
|
||
- download initiation depends on metadata-generated `pinswg_hashlink` stored in document rows within `documentDetailsObj`
|
||
|
||
#### Service Layer
|
||
|
||
##### Visible service/helper modules
|
||
|
||
- `components/utils/downloads.js`
|
||
- acts as the main browser-side download helper in the reviewed published-document path
|
||
|
||
- `components/utils/downloadmanager.js`
|
||
- acts as the visible queue/orchestration helper
|
||
|
||
##### Important boundary note
|
||
|
||
- this journey does not use a separate frontend `actions/services/*` download helper for published documents in the reviewed case-document path
|
||
- instead, the browser fetches the generated proxy URL directly
|
||
|
||
#### API Layer
|
||
|
||
##### Principal route
|
||
|
||
- `pages/api/documents/download/[id].js`
|
||
|
||
##### Route-family characteristics
|
||
|
||
- `documents/download/[id].js`
|
||
- **document download proxy**
|
||
- requires `id` path param and `hash` query param
|
||
- obtains access token via `getToken()`
|
||
- forwards request to relay-backed `documents/download/{id}?hash=...`
|
||
- streams response to the browser with download headers
|
||
- redirects to `/filenotavailable` on invalid input or downstream failure
|
||
- includes retry behaviour before giving up
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM document delivery via Azure Relay**
|
||
- visible downstream source of the streamed document response
|
||
|
||
- **Local proxy processing**
|
||
- request validation for `id` and `hash`
|
||
- retry handling
|
||
- response header setting
|
||
- browser-stream handoff
|
||
|
||
- **Analytics**
|
||
- browser-side `DownloadedFile` event emitted after successful client download flow
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Document metadata row
|
||
→ pinswg_hashlink
|
||
→ /api/documents/download/[id]
|
||
→ relay-backed document stream
|
||
→ browser file download
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ click document link/button in `components/case/documents.js`
|
||
→ `components/utils/downloads.js`
|
||
→ fetch `detailsObj.pinswg_hashlink`
|
||
→ `pages/api/documents/download/[id].js`
|
||
→ `getToken()`
|
||
→ relay-backed `documents/download/{id}?hash=...`
|
||
→ streamed response returned to browser
|
||
→ blob URL download trigger
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `components/case/documents.js`
|
||
- `components/utils/downloads.js`
|
||
- `components/utils/downloadmanager.js`
|
||
- `pages/api/documents/download/[id].js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/endpoint/getsearchdocumentdetails_api.js`
|
||
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
|
||
- `actions/core/token.js`
|
||
- `actions/core/logger.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- continuity between generated hash links and proxy-route expectations
|
||
- filename extraction from response headers
|
||
- retry and failure redirect behaviour
|
||
- divergence between discovery metadata and actual downloadable document reference
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- direct user-visible download behaviour
|
||
- download success depends on cross-boundary continuity between metadata shaping and proxy delivery
|
||
- failure path redirects to a dedicated not-available route rather than returning document metadata errors in-place
|
||
|
||
### Document Delivery Architecture
|
||
|
||
#### Visible architecture
|
||
|
||
```text
|
||
User
|
||
→ Page
|
||
→ Service
|
||
→ API
|
||
→ CRM Metadata
|
||
→ Download Proxy
|
||
→ Document Delivery
|
||
```
|
||
|
||
#### Visible delivery flow
|
||
|
||
```text
|
||
User
|
||
→ case documents UI
|
||
→ search document metadata route
|
||
→ CRM published document metadata
|
||
→ metadata row includes pinswg_hashlink
|
||
→ browser fetches /api/documents/download/[id]?hash=...
|
||
→ download proxy forwards to relay-backed documents/download/{id}
|
||
→ streamed file delivered to browser
|
||
```
|
||
|
||
#### Document metadata source
|
||
|
||
- visible source: `pinswg_documents` metadata queried via relay-backed endpoint routes
|
||
- key visible metadata fields include:
|
||
- `pinswg_isharedocumentreference`
|
||
- `pinswg_name`
|
||
- `pinswg_latestpublisheddate`
|
||
- `pinswg_documentpublisheddate`
|
||
- `pinswg_isharedocumentlocations`
|
||
|
||
#### Download mechanism
|
||
|
||
- metadata routes generate `pinswg_hashlink`
|
||
- browser fetches the hash link
|
||
- proxy streams the binary response
|
||
- browser creates a blob-backed local download
|
||
|
||
#### Proxy behaviour
|
||
|
||
- validates presence of `id` and `hash`
|
||
- retrieves access token
|
||
- retries the downstream fetch up to the visible configured attempt count
|
||
- sets `Content-Disposition`
|
||
- streams binary data to the browser
|
||
- redirects to `/filenotavailable` when download cannot be served
|
||
|
||
### Future API Grouping Assessment
|
||
|
||
This section is a **future grouping assessment** only.
|
||
|
||
It is **not an implementation recommendation**.
|
||
|
||
| Current route | Journey owner | Integration touched | Future grouping candidate | Migration caution |
|
||
| --------------------------------------------------------- | --------------------------------------- | ------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||
| `pages/api/documents/download/[id].js` | published document retrieval / download | CRM Relay | candidate for `api/documents/download` | dedicated streaming proxy with redirect-on-failure behaviour; migration caution because callers depend on binary delivery rather than JSON shape |
|
||
| `pages/api/endpoint/getsearchdocumentdetails_api.js` | published document discovery | CRM Relay | candidate for `api/documents/discovery` | generates `pinswg_hashlink` consumed by download flow; migration caution because metadata and download continuity are tightly coupled |
|
||
| `pages/api/endpoint/getsearchdocumentdetailspaged_api.js` | published document discovery | CRM Relay | candidate for `api/documents/discovery` | carries paging, sorting, and document-type filtering semantics; migration caution because UI list behaviour depends on current contract |
|
||
| `pages/api/endpoint/getsearchdocumentTypes_api.js` | published document discovery | CRM Relay | candidate for `api/documents/discovery` | grouped bucket/count output is UI-shaped rather than raw CRM output; migration caution because filter UI depends on this grouped format |
|
||
| `pages/api/endpoint/getsearchdocumenthistory_api.js` | published document discovery support | CRM Relay | candidate for `api/documents/history` | route is visible in the family but not surfaced in the reviewed UI path; migration caution because unseen callers may still depend on contract |
|
||
| `pages/api/endpoint/getsearchdocumenthistorypaged_api.js` | published document discovery support | CRM Relay | candidate for `api/documents/history` | paged/history variant appears parallel to non-paged history route; migration caution because contract usage was not fully surfaced in this slice |
|
||
|
||
#### Classification notes
|
||
|
||
- **candidate for `api/documents/discovery`**
|
||
- routes whose clearest visible responsibility is published-document metadata retrieval, shaping, grouping, paging, or filtering
|
||
- **candidate for `api/documents/history`**
|
||
- routes whose clearest visible responsibility is history metadata rather than current document-list presentation
|
||
- **candidate for `api/documents/download`**
|
||
- routes whose clearest visible responsibility is binary document delivery
|
||
|
||
### Discovery vs Download Comparison
|
||
|
||
#### Shared APIs
|
||
|
||
- both journeys depend on:
|
||
- metadata-generated `pinswg_hashlink`
|
||
- document reference continuity across search/document route family and download proxy route
|
||
|
||
#### Shared integrations
|
||
|
||
- both touch:
|
||
- CRM via Azure Relay
|
||
|
||
#### Shared ownership assumptions
|
||
|
||
- both assume the document journey is keyed by:
|
||
- case incident id for discovery
|
||
- document shared reference/id for download
|
||
- both assume published-web filtering occurs before a document becomes user-downloadable in the visible UI path
|
||
|
||
#### Where the journeys diverge
|
||
|
||
- discovery is metadata/list oriented:
|
||
- grouping
|
||
- filtering
|
||
- sorting
|
||
- pagination
|
||
- bilingual label presentation
|
||
- download is binary-delivery oriented:
|
||
- proxy forwarding
|
||
- stream handling
|
||
- filename extraction
|
||
- failure redirect
|
||
|
||
### Architectural Flows
|
||
|
||
#### Published Document Discovery
|
||
|
||
```text
|
||
User
|
||
→ search results
|
||
→ case detail route
|
||
→ case documents component
|
||
→ getSearchDocumentTypes / getSearchDocumentDetails / getSearchDocumentDetailsPaged
|
||
→ CRM published-document metadata
|
||
→ documentDetailsObj
|
||
→ visible document list
|
||
```
|
||
|
||
#### Published Document Retrieval / Download
|
||
|
||
```text
|
||
User
|
||
→ click published document link
|
||
→ metadata row pinswg_hashlink
|
||
→ /api/documents/download/[id]
|
||
→ relay-backed document stream
|
||
→ browser blob download
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Published Document Discovery
|
||
|
||
- start with:
|
||
- `pages/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/documents.js`
|
||
- `actions/services/searchDirectService.js`
|
||
- `pages/api/endpoint/{getsearchdocumentdetails_api,getsearchdocumentdetailspaged_api,getsearchdocumentTypes_api}.js`
|
||
- `store/searchOutput/{action,reducer}.js`
|
||
|
||
#### Published Document Retrieval / Download
|
||
|
||
- start with:
|
||
- `components/case/documents.js`
|
||
- `components/utils/downloads.js`
|
||
- `components/utils/downloadmanager.js`
|
||
- `pages/api/documents/download/[id].js`
|
||
- adjacent metadata generators in `pages/api/endpoint/getsearchdocumentdetails*_api.js`
|
||
|
||
### Risk Classification
|
||
|
||
- Published Document Discovery: **High**
|
||
- Published Document Retrieval / Download: **High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 7
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/portal-api-platform-assessment.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Journey pages / components / services / state:
|
||
|
||
- `components/search/searchresults.js`
|
||
- `pages/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/documents.js`
|
||
- `components/utils/downloads.js`
|
||
- `components/utils/downloadmanager.js`
|
||
- `actions/services/searchService.js`
|
||
- `actions/services/searchDirectService.js`
|
||
- `store/searchOutput/action.js`
|
||
- `store/searchOutput/reducer.js`
|
||
|
||
API files:
|
||
|
||
- `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`
|
||
|
||
#### Searches performed for Slice 7
|
||
|
||
- `pages`: `documents/download|getsearchdocumentdetails|getsearchdocumenthistory|getsearchdocumentTypes|filenotavailable`
|
||
- `components`: `document|download|filenotavailable|DocumentDetails|docsOffline|showFilteredDocs`
|
||
- `actions/services`: `getSearchDocumentDetails|getSearchDocumentTypes|getSearchDocumentDetailsPaged|download`
|
||
- `store`: `documentDetailsObj|setDocumentDetails|setDocumentHistory`
|
||
- `pages/api`: `getsearchdocumentdetails|getsearchdocumenthistory|getsearchdocumentTypes|download/[id]|hashAPIPath|relayGet(`
|
||
|
||
#### Limitations for Slice 7
|
||
|
||
- This slice was intentionally limited to visible published-document discovery and download architecture.
|
||
- It did not speculate about security posture, authorization posture, or relay-side implementation beyond the visible frontend code.
|
||
- It did not widen into draft/blob document upload flows, appeal-PDF download flows, or admin/latest-document reporting flows.
|
||
- It did not execute runtime downloads.
|
||
- It did not infer active use of document history beyond visible route presence, because the reviewed UI path did not surface it.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. Discovery and download are separate route families but are tightly coupled by generated `pinswg_hashlink` values.
|
||
2. `docsOffline` visibly suppresses live document-link behaviour, so maintainers should treat document availability messaging as part of the journey architecture.
|
||
3. The reviewed user-facing discovery path is case-detail-centric rather than a standalone document page, so changes can affect search-to-case continuity.
|
||
4. Document history routes exist in the API family, but their visible user-facing ownership is weaker than current document discovery in the reviewed slice.
|
||
5. Download behaviour is split between server-side streaming in `/api/documents/download/[id]` and client-side blob handling in `components/utils/downloads.js`.
|
||
|
||
### Validation Performed
|
||
|
||
- Confirmed the required Slice 7 context files were read.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Traced the visible discovery path from search-to-case navigation into the case documents component.
|
||
- Traced the visible delivery path from metadata-generated hash link into the download proxy and browser download helper.
|
||
- Confirmed that document history routes are present but not visibly surfaced by the reviewed case-document UI path.
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Case Messages / Notices and Related Published Case Communications**
|
||
|
||
This would extend the journey map into another public case-detail-adjacent read flow, staying within discovery-only architecture work and preserving continuity with the already-mapped case, document, notification, and watchlist slices.
|
||
|
||
---
|
||
|
||
## Slice 8 — Case Messages / Notices and Related Published Case Communications
|
||
|
||
### Files Modified
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
### Findings
|
||
|
||
- The visible case-communication architecture is centered on **case-detail presentation**, not outbound delivery.
|
||
- The strongest visible message path is:
|
||
- case route bootstrap
|
||
- `getCaseMessage(incidentid)`
|
||
- `messagesObj`
|
||
- `CaseNoticeBanner`
|
||
- user-visible notice content on the case details tab
|
||
- The reviewed case-message route retrieves CRM `tasks` records filtered to subjects containing `Banner`, which is the clearest visible source of public case-level notices in this slice.
|
||
- Published case communications are therefore distinct from:
|
||
- **Published Documents**, which are metadata + download driven
|
||
- **Notifications / Email**, which are outbound communication driven
|
||
- SIPS events and SIPS media are adjacent published case communications in the case journey, but they are visibly surfaced as separate tabs and a live-event banner rather than being part of the same `messagesObj` notice payload.
|
||
|
||
### Case Messages / Notices Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a user to see time-bounded public case notices or banner-style communications associated with a case.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how PEDW loads and renders public case notices directly in the case-detail experience, using a dedicated message route and a case-page presentation component rather than a document or notification delivery mechanism.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/caseNoticeBanner.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### Public case loader
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- bootstraps case detail through search-family reads
|
||
- separately retrieves messages with:
|
||
- `getCaseMessage(searchResultsObj.value[0].incidentid)`
|
||
- passes `messagesObj` into the case page props
|
||
|
||
##### DNS case loader
|
||
|
||
- `pages/dns/[developmentName].js`
|
||
- bootstraps DNS case detail through DNS search-family reads
|
||
- separately retrieves messages with:
|
||
- `getCaseMessage(searchResultsObj.value[0].incidentid)`
|
||
- passes `messagesObj` into the case page props
|
||
|
||
##### My Portal case loader
|
||
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- bootstraps portal case detail with authenticated portal context plus case search/detail data
|
||
- separately retrieves messages with:
|
||
- `getCaseMessage(searchResultsObj.value[0].incidentid)`
|
||
- passes `messagesObj` into the case page props
|
||
|
||
##### Case page presentation bootstrap
|
||
|
||
- `components/case.js`
|
||
- passes `messagesObj` into `components/case/summary.js`
|
||
|
||
- `components/case/summary.js`
|
||
- renders `CaseNoticeBanner` inside the `case-details` tab when:
|
||
- `props.messagesObj["@odata.count"] > 0`
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary ownership
|
||
|
||
- `messagesObj`
|
||
- is page-prop owned in the reviewed path
|
||
- is not stored in a dedicated Redux slice in the reviewed case-message flow
|
||
|
||
##### Adjacent state
|
||
|
||
- `store/searchOutput/reducer.js`
|
||
- owns adjacent case-detail communication state for:
|
||
- `eventDetailsObj`
|
||
- `mediaDetailsObj`
|
||
- does not own `messagesObj`
|
||
|
||
- `store/currentView/reducer.js`
|
||
- participates only indirectly through case route/context continuity
|
||
|
||
##### UI ownership
|
||
|
||
- `components/case/caseNoticeBanner.js`
|
||
- owns the visible interpretation and rendering of message rows
|
||
- applies date-window checks and bilingual content splitting in the UI layer
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `getCaseMessage(searchString)`
|
||
- adjacent communication-related helpers:
|
||
- `getSIPSEvents(caseid)`
|
||
- `getSIPSMedia(caseid)`
|
||
|
||
- `actions/services/caseService.js`
|
||
- thin re-export layer for the above helpers
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
- adjacent directly relevant communication routes:
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
|
||
##### Route-family characteristics
|
||
|
||
- `getcasemessage_api.js`
|
||
- **CRM relay read for case banner messages**
|
||
- requires `id`
|
||
- queries CRM `tasks`
|
||
- filters by:
|
||
- `_regardingobjectid_value eq caseId`
|
||
- `contains(subject, 'Banner')`
|
||
- `statuscode ne 5`
|
||
- orders by `createdon desc`
|
||
|
||
- `getsipsevents_api.js`
|
||
- **CRM relay read for published event records**
|
||
- separate case-communication route family for event-tab content
|
||
|
||
- `getsipsmedia_api.js`
|
||
- **CRM relay read for published media/event recordings**
|
||
- separate case-communication route family for media-tab content
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary source of visible case notices/messages
|
||
- also the source of adjacent SIPS communication content
|
||
|
||
- **Local-only processing**
|
||
- date-window filtering in `CaseNoticeBanner`
|
||
- bilingual subject/description splitting in the UI layer
|
||
- case-tab placement and notice rendering
|
||
|
||
- **NextAuth**
|
||
- not required for the public case-message path
|
||
- used only in the authenticated myportal case variant upstream of the same message retrieval call
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Case incident id
|
||
→ getCaseMessage(incidentid)
|
||
→ CRM tasks filtered to Banner subjects
|
||
→ messagesObj page prop
|
||
→ CaseNoticeBanner
|
||
→ visible case notice
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ case route (`pages/case/[ticketnumber].js` or DNS/portal variant)
|
||
→ case bootstrap via search family
|
||
→ `caseService.getCaseMessage(incidentid)`
|
||
→ `pages/api/endpoint/getcasemessage_api.js`
|
||
→ `relayGet(...)`
|
||
→ Azure Relay
|
||
→ Dynamics 365 CRM `tasks`
|
||
→ `messagesObj`
|
||
→ `components/case/summary.js`
|
||
→ `CaseNoticeBanner`
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/caseNoticeBanner.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
- `store/searchOutput/action.js`
|
||
- `store/searchOutput/reducer.js`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- message filtering assumptions based on `subject` containing `Banner`
|
||
- bilingual content splitting conventions in `subject` and `description`
|
||
- date-window visibility logic in `CaseNoticeBanner`
|
||
- page-prop ownership of `messagesObj`, because it is not normalized into Redux in the reviewed path
|
||
|
||
#### Risk Classification
|
||
|
||
**High**
|
||
|
||
Reasoning:
|
||
|
||
- public case-page communication is user-visible and contract-sensitive
|
||
- message meaning is shaped partly in the UI layer rather than only in the API layer
|
||
- the same case journey mixes messages, documents, events, media, and status presentation
|
||
|
||
### Related Published Case Communications Journey Map
|
||
|
||
#### Purpose
|
||
|
||
Business purpose:
|
||
|
||
- allows a user to see related published case communications around the case beyond banner notices, where those communications are directly surfaced in the case-detail journey.
|
||
|
||
Maintainer purpose:
|
||
|
||
- this journey shows how PEDW presents adjacent published communication surfaces, especially SIPS live-event, events, and media content, and how those differ from notice banners while still participating in the same case-page communication experience.
|
||
|
||
#### Primary Entry Points
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case/summary.js`
|
||
- adjacent case communication components:
|
||
- `components/case/events.js`
|
||
- `components/case/media.js`
|
||
|
||
#### Loaders / Initialisation
|
||
|
||
##### SIPS communication bootstrap
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- conditionally load SIPS event records when appeal case type is `846040002`
|
||
- load:
|
||
- `getSIPSEvents(searchDetailsObj[0].value[0].pinswg_sipsid)`
|
||
- `getSIPSMedia(searchResultsObj.value[0].incidentid)`
|
||
- dispatch results into Redux:
|
||
- `setEventDetails(eventsObj)`
|
||
- `setMediaDetails(mediaObj)`
|
||
|
||
##### Case-summary communication presentation
|
||
|
||
- `components/case/summary.js`
|
||
- derives:
|
||
- `hasEventsTabData`
|
||
- `hasMediaTabData`
|
||
- `livePublishedEvent`
|
||
- renders a GOV.UK notification banner for a live published event when available
|
||
- renders separate `Events` and `Media` tabs when corresponding data exists
|
||
|
||
#### State Ownership
|
||
|
||
##### Primary slices
|
||
|
||
- `store/searchOutput/reducer.js`
|
||
- owns:
|
||
- `eventDetailsObj`
|
||
- `mediaDetailsObj`
|
||
- this is the primary visible state owner for adjacent published case communications in the reviewed path
|
||
|
||
##### UI ownership
|
||
|
||
- `components/case/summary.js`
|
||
- owns the live-event banner selection logic through derived view state
|
||
|
||
#### Service Layer
|
||
|
||
##### Primary service modules
|
||
|
||
- `actions/services/caseDirectService.js`
|
||
- `getSIPSEvents(caseid)`
|
||
- `getSIPSMedia(caseid)`
|
||
|
||
#### API Layer
|
||
|
||
##### Principal routes
|
||
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
|
||
##### Route-family characteristics
|
||
|
||
- `getsipsevents_api.js`
|
||
- **CRM relay read for event records**
|
||
- requires `caseid`
|
||
- reads `pinswg_sipsevents`
|
||
|
||
- `getsipsmedia_api.js`
|
||
- **CRM relay read for published event recordings/media**
|
||
- requires `caseid`
|
||
- filters to `pinswg_publishtoweb eq true`
|
||
- returns published recording metadata and URLs
|
||
|
||
#### Integration Boundaries
|
||
|
||
- **CRM via Azure Relay**
|
||
- primary source of event/media communication records
|
||
|
||
- **Local-only processing**
|
||
- live-event derivation and banner placement
|
||
- case-tab presentation
|
||
|
||
#### Ownership Model
|
||
|
||
```text
|
||
Case / SIPS context
|
||
→ getSIPSEvents / getSIPSMedia
|
||
→ Redux eventDetailsObj / mediaDetailsObj
|
||
→ case summary tabs and live-event banner
|
||
```
|
||
|
||
#### Architectural Flow
|
||
|
||
User
|
||
→ case route
|
||
→ SIPS-specific conditional bootstrap
|
||
→ `getSIPSEvents(...)` / `getSIPSMedia(...)`
|
||
→ `getsipsevents_api.js` / `getsipsmedia_api.js`
|
||
→ CRM via relay
|
||
→ Redux event/media state
|
||
→ case summary live-event banner and tabs
|
||
|
||
#### Change Entry Set
|
||
|
||
##### First files to inspect
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case/summary.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
- `store/searchOutput/action.js`
|
||
- `store/searchOutput/reducer.js`
|
||
|
||
##### Likely adjacent files
|
||
|
||
- `components/case/events.js`
|
||
- `components/case/media.js`
|
||
- `lib/domain/case-lifecycle/*`
|
||
|
||
##### Highest-risk areas
|
||
|
||
- direct coupling between case type checks and SIPS communication loading
|
||
- live-event banner derivation logic in the case-summary layer
|
||
- adjacency between event/media communications and message/document/status tabs in one page shell
|
||
|
||
#### Risk Classification
|
||
|
||
**Medium-High**
|
||
|
||
Reasoning:
|
||
|
||
- user-visible communication content on public case pages
|
||
- conditional SIPS-specific branching adds hidden coupling
|
||
- adjacent but distinct from the main case-message banner route
|
||
|
||
### Case Communication Architecture
|
||
|
||
#### Visible architecture
|
||
|
||
```text
|
||
Case
|
||
→ message/notice source
|
||
→ case-detail presentation
|
||
→ user-visible communication
|
||
```
|
||
|
||
#### Visible communication flow
|
||
|
||
```text
|
||
Case incident id
|
||
→ getcasemessage_api / getsipsevents_api / getsipsmedia_api
|
||
→ case-detail props or Redux state
|
||
→ case summary / notice banner / event-media tabs
|
||
→ user-visible communication on case page
|
||
```
|
||
|
||
#### Data source
|
||
|
||
- banner notices/messages:
|
||
- CRM `tasks` records filtered by `contains(subject, 'Banner')`
|
||
- related SIPS communications:
|
||
- CRM `pinswg_sipsevents`
|
||
- CRM `pinswg_eventrecordings`
|
||
|
||
#### Route family
|
||
|
||
- primary notice route:
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
- adjacent communication routes:
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
|
||
#### State ownership
|
||
|
||
- `messagesObj`
|
||
- page-prop owned
|
||
- `eventDetailsObj` / `mediaDetailsObj`
|
||
- Redux owned via `store/searchOutput`
|
||
|
||
#### UI ownership
|
||
|
||
- `components/case/summary.js`
|
||
- main case-page owner of communication placement
|
||
- `components/case/caseNoticeBanner.js`
|
||
- owner of banner-message rendering
|
||
|
||
#### Where this differs from documents and notifications
|
||
|
||
- **Messages / Notices**
|
||
- case-page presentation of case-linked communications
|
||
- **Published Documents**
|
||
- document metadata retrieval + binary delivery path
|
||
- **Notifications / Email**
|
||
- outbound communication to a recipient rather than on-page case presentation
|
||
|
||
### Future API Grouping Assessment
|
||
|
||
This section is a **future grouping assessment** only.
|
||
|
||
It is **not an implementation recommendation**.
|
||
|
||
| Current route | Journey owner | Integration touched | Future grouping candidate | Migration caution |
|
||
| ------------------------------------------ | ------------------------------------- | ------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `pages/api/endpoint/getcasemessage_api.js` | case messages / notices | CRM Relay | candidate for `api/cases/messages` | route is the clearest visible public notice/banner source; migration caution because case pages currently depend on its specific tasks/banner shape |
|
||
| `pages/api/endpoint/getsipsevents_api.js` | related published case communications | CRM Relay | candidate for `api/cases/events` | SIPS-specific route is conditionally loaded from case pages; migration caution because ownership is case-type dependent rather than universally case-owned |
|
||
| `pages/api/endpoint/getsipsmedia_api.js` | related published case communications | CRM Relay | candidate for `api/cases/events` or `api/shared` | media is adjacent communication content but also its own tab family; migration caution because it overlaps event/media presentation concerns |
|
||
| `pages/api/notices/index.js` | shared site notice support | Local-only | candidate for `api/shared` or unclear / historical | static notice route is not part of case-message retrieval; migration caution because it appears to be site-level notice support rather than case-owned |
|
||
|
||
#### Classification notes
|
||
|
||
- **candidate for `api/cases/messages`**
|
||
- routes whose clearest visible owner is case-page notice/message presentation
|
||
- **candidate for `api/cases/events`**
|
||
- routes whose clearest visible owner is case-page event/media communication content
|
||
- **candidate for `api/shared`**
|
||
- routes with broader site-level or cross-journey notice/support behaviour
|
||
- **unclear / historical**
|
||
- routes whose visible ownership is weaker or not clearly part of the active case-message journey
|
||
|
||
### Messages vs Documents vs Notifications Comparison
|
||
|
||
#### Where they overlap
|
||
|
||
- all three are user-visible communication surfaces in the wider platform
|
||
- all can be associated with case context
|
||
- messages and documents are both directly surfaced on case pages
|
||
- notifications and messages can both convey case-related information, but in different delivery models
|
||
|
||
#### Where they differ
|
||
|
||
- **Case Messages / Notices**
|
||
- case-page presentation
|
||
- time-bounded or banner-style communication
|
||
- no download proxy
|
||
- no outbound send path in the reviewed journey
|
||
|
||
- **Published Documents**
|
||
- metadata list + binary delivery
|
||
- document-type grouping, sorting, filtering, and download behaviour
|
||
|
||
- **Notifications / Email**
|
||
- outbound communication
|
||
- Notify/template driven
|
||
- recipient-address oriented rather than case-tab oriented
|
||
|
||
#### Which one is case-page presentation
|
||
|
||
- Case Messages / Notices
|
||
|
||
#### Which one is document delivery
|
||
|
||
- Published Documents
|
||
|
||
#### Which one is outbound communication
|
||
|
||
- Notifications / Email
|
||
|
||
### Architectural Flows
|
||
|
||
#### Case Messages / Notices
|
||
|
||
```text
|
||
User
|
||
→ case route bootstrap
|
||
→ getCaseMessage(incidentid)
|
||
→ getcasemessage_api
|
||
→ CRM Banner task records
|
||
→ messagesObj
|
||
→ CaseNoticeBanner
|
||
```
|
||
|
||
#### Related Published Case Communications
|
||
|
||
```text
|
||
User
|
||
→ case route bootstrap
|
||
→ conditional SIPS event/media fetches
|
||
→ getsipsevents_api / getsipsmedia_api
|
||
→ Redux event/media state
|
||
→ live-event banner / event-media tabs
|
||
```
|
||
|
||
### Change Entry Sets
|
||
|
||
#### Case Messages / Notices
|
||
|
||
- start with:
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/caseNoticeBanner.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
|
||
#### Related Published Case Communications
|
||
|
||
- start with:
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case/summary.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `pages/api/endpoint/{getsipsevents_api,getsipsmedia_api}.js`
|
||
- `store/searchOutput/{action,reducer}.js`
|
||
|
||
### Risk Classification
|
||
|
||
- Case Messages / Notices: **High**
|
||
- Related Published Case Communications: **Medium-High**
|
||
|
||
### Investigation Method
|
||
|
||
#### Files reviewed for Slice 8
|
||
|
||
Required context re-read:
|
||
|
||
- `context/journey-architecture-map.md`
|
||
- `context/api-route-map.md`
|
||
- `context/portal-api-platform-assessment.md`
|
||
- `context/architecture.md`
|
||
- `context/integration-map.md`
|
||
- `memory-bank/change-log.md`
|
||
|
||
Journey pages / components / services / state:
|
||
|
||
- `pages/case/[ticketnumber].js`
|
||
- `pages/dns/[developmentName].js`
|
||
- `pages/myportal/case/[ticketnumber].js`
|
||
- `components/case.js`
|
||
- `components/case/summary.js`
|
||
- `components/case/caseNoticeBanner.js`
|
||
- `actions/services/caseService.js`
|
||
- `actions/services/caseDirectService.js`
|
||
- `store/searchOutput/action.js`
|
||
- `store/searchOutput/reducer.js`
|
||
|
||
API files:
|
||
|
||
- `pages/api/endpoint/getcasemessage_api.js`
|
||
- `pages/api/endpoint/getsipsevents_api.js`
|
||
- `pages/api/endpoint/getsipsmedia_api.js`
|
||
- `pages/api/notices/index.js`
|
||
|
||
#### Searches performed for Slice 8
|
||
|
||
- `components/case`: `messagesObj|getCaseMessage|notice|banner|message`
|
||
- `pages`: `getCaseMessage|messagesObj|getcasemessage_api|notice|message`
|
||
- `actions/services`: `getCaseMessage|getSIPSEvents|getSIPSMedia|message|notice`
|
||
- `pages/api/endpoint`: `getcasemessage_api|getsipsevents_api|getsipsmedia_api|message|notice`
|
||
- `store`: `messagesObj|message|notice|eventDetailsObj|mediaDetailsObj`
|
||
|
||
#### Limitations for Slice 8
|
||
|
||
- This slice was intentionally limited to visible case-message / notice / communication flows.
|
||
- It did not widen into outbound notification delivery or document download behaviour beyond comparison.
|
||
- It did not assess communication policy correctness or content governance.
|
||
- It did not infer a dedicated Redux ownership model for `messagesObj` where none was visibly present.
|
||
- It did not execute runtime case-page flows.
|
||
|
||
### Risks / Cautions
|
||
|
||
1. The main notice source is identified through a CRM `tasks` query filtered by `contains(subject, 'Banner')`, so behaviour depends on content conventions as well as route logic.
|
||
2. `CaseNoticeBanner` performs visible bilingual splitting and date-window checks in the UI layer, which makes presentation logic part of the architectural behaviour.
|
||
3. `messagesObj` is page-prop owned while adjacent event/media communications are Redux owned, so communication state is split across ownership models.
|
||
4. SIPS live-event/media communications overlap with case communications, but they are a separate visible route/state family from banner notices.
|
||
5. The static `pages/api/notices/index.js` route exists as a notice-like support surface but is not part of the reviewed case-message loading path.
|
||
|
||
### Validation Performed
|
||
|
||
- Confirmed the required Slice 8 context files were read.
|
||
- Performed non-destructive code reading and targeted searches only.
|
||
- Traced the visible case-message flow from case loader to `CaseNoticeBanner` presentation.
|
||
- Traced directly relevant SIPS event/media communication overlap only where it is visibly part of the case page.
|
||
- Compared message/notice presentation against the already documented document and notification slices without reopening those journeys.
|
||
- No runtime code changed.
|
||
- No lint/tests run because this was documentation-only work.
|
||
|
||
### Recommendation
|
||
|
||
Next journey slice only:
|
||
|
||
- **Case Status / Lifecycle Presentation and Related Published Timeline Signals**
|
||
|
||
This would extend the journey map into another case-detail-adjacent presentation slice that naturally follows messages, notices, events, media, and documents while remaining discovery-only.
|