@defra/forms-engine-plugin 2.1.4 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -2
  2. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  3. package/.server/server/plugins/engine/components/ComponentCollection.d.ts +2 -2
  4. package/.server/server/plugins/engine/components/ComponentCollection.js +1 -1
  5. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  6. package/.server/server/plugins/engine/components/TelephoneNumberField.js +1 -1
  7. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
  8. package/.server/server/plugins/engine/components/YesNoField.js +1 -1
  9. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  10. package/.server/server/plugins/engine/components/{helpers.d.ts → helpers/components.d.ts} +3 -10
  11. package/.server/server/plugins/engine/components/{helpers.js → helpers/components.js} +16 -29
  12. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -0
  13. package/.server/server/plugins/engine/components/helpers/index.d.ts +11 -0
  14. package/.server/server/plugins/engine/components/helpers/index.js +15 -0
  15. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -0
  16. package/.server/server/plugins/engine/helpers.d.ts +1 -1
  17. package/.server/server/plugins/engine/helpers.js +1 -1
  18. package/.server/server/plugins/engine/helpers.js.map +1 -1
  19. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -2
  20. package/.server/server/plugins/engine/models/FormModel.js +2 -2
  21. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  22. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
  23. package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
  24. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  25. package/.server/server/plugins/engine/models/types.d.ts +2 -2
  26. package/.server/server/plugins/engine/models/types.js.map +1 -1
  27. package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +6 -0
  28. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +23 -0
  29. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -0
  30. package/.server/server/plugins/engine/outputFormatters/human/v1.d.ts +2 -2
  31. package/.server/server/plugins/engine/outputFormatters/human/v1.js +3 -2
  32. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  33. package/.server/server/plugins/engine/outputFormatters/index.d.ts +4 -3
  34. package/.server/server/plugins/engine/outputFormatters/index.js +4 -0
  35. package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -1
  36. package/.server/server/plugins/engine/outputFormatters/machine/v1.d.ts +2 -2
  37. package/.server/server/plugins/engine/outputFormatters/machine/v1.js +2 -2
  38. package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
  39. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +4 -1
  40. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  41. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +1 -1
  42. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  43. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +6 -5
  44. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  45. package/.server/server/plugins/engine/pageControllers/helpers/index.d.ts +9 -0
  46. package/.server/server/plugins/engine/pageControllers/helpers/index.js +15 -0
  47. package/.server/server/plugins/engine/pageControllers/helpers/index.js.map +1 -0
  48. package/.server/server/plugins/engine/pageControllers/{helpers.d.ts → helpers/pages.d.ts} +1 -10
  49. package/.server/server/plugins/engine/pageControllers/{helpers.js → helpers/pages.js} +2 -17
  50. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -0
  51. package/.server/server/plugins/engine/routes/index.d.ts +1 -1
  52. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  53. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  54. package/.server/server/plugins/engine/services/localFormsService.d.ts +1 -1
  55. package/.server/server/plugins/engine/services/notifyService.d.ts +7 -2
  56. package/.server/server/plugins/engine/services/notifyService.js +12 -3
  57. package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
  58. package/.server/server/plugins/engine/types/index.d.ts +10 -0
  59. package/.server/server/plugins/engine/types/index.js +4 -0
  60. package/.server/server/plugins/engine/types/index.js.map +1 -0
  61. package/.server/server/plugins/engine/types/schema.d.ts +5 -0
  62. package/.server/server/plugins/engine/types/schema.js +24 -0
  63. package/.server/server/plugins/engine/types/schema.js.map +1 -0
  64. package/.server/server/plugins/engine/types.d.ts +39 -3
  65. package/.server/server/plugins/engine/types.js +4 -0
  66. package/.server/server/plugins/engine/types.js.map +1 -1
  67. package/.server/server/plugins/nunjucks/filters/answer.js +2 -2
  68. package/.server/server/plugins/nunjucks/filters/answer.js.map +1 -1
  69. package/.server/server/plugins/nunjucks/filters/answer.test.js +2 -2
  70. package/.server/server/plugins/nunjucks/filters/answer.test.js.map +1 -1
  71. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  72. package/.server/server/plugins/nunjucks/filters/page.d.ts +1 -1
  73. package/.server/server/types.d.ts +1 -1
  74. package/.server/server/types.js.map +1 -1
  75. package/package.json +6 -1
  76. package/src/server/plugins/engine/components/AutocompleteField.test.ts +1 -1
  77. package/src/server/plugins/engine/components/CheckboxesField.test.ts +1 -1
  78. package/src/server/plugins/engine/components/ComponentBase.ts +2 -2
  79. package/src/server/plugins/engine/components/ComponentCollection.ts +2 -2
  80. package/src/server/plugins/engine/components/DatePartsField.test.ts +1 -1
  81. package/src/server/plugins/engine/components/Details.test.ts +1 -1
  82. package/src/server/plugins/engine/components/EmailAddressField.test.ts +1 -1
  83. package/src/server/plugins/engine/components/FileUploadField.test.ts +2 -2
  84. package/src/server/plugins/engine/components/Html.test.ts +1 -1
  85. package/src/server/plugins/engine/components/InsetText.test.ts +1 -1
  86. package/src/server/plugins/engine/components/List.test.ts +1 -1
  87. package/src/server/plugins/engine/components/Markdown.test.ts +1 -1
  88. package/src/server/plugins/engine/components/MonthYearField.test.ts +1 -1
  89. package/src/server/plugins/engine/components/MultilineTextField.test.ts +1 -1
  90. package/src/server/plugins/engine/components/NumberField.test.ts +1 -1
  91. package/src/server/plugins/engine/components/RadiosField.test.ts +1 -1
  92. package/src/server/plugins/engine/components/SelectField.test.ts +1 -1
  93. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +1 -1
  94. package/src/server/plugins/engine/components/TelephoneNumberField.ts +1 -1
  95. package/src/server/plugins/engine/components/TextField.test.ts +1 -1
  96. package/src/server/plugins/engine/components/UkAddressField.test.ts +1 -1
  97. package/src/server/plugins/engine/components/YesNoField.test.ts +1 -1
  98. package/src/server/plugins/engine/components/YesNoField.ts +1 -1
  99. package/src/server/plugins/engine/components/{helpers.ts → helpers/components.ts} +41 -77
  100. package/src/server/plugins/engine/components/{helpers.test.ts → helpers/helpers.test.ts} +1 -1
  101. package/src/server/plugins/engine/components/helpers/index.ts +38 -0
  102. package/src/server/plugins/engine/helpers.test.ts +1 -1
  103. package/src/server/plugins/engine/helpers.ts +2 -2
  104. package/src/server/plugins/engine/models/FormModel.ts +2 -2
  105. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +1 -1
  106. package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -2
  107. package/src/server/plugins/engine/models/types.ts +4 -4
  108. package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +506 -0
  109. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +53 -0
  110. package/src/server/plugins/engine/outputFormatters/human/v1.ts +8 -6
  111. package/src/server/plugins/engine/outputFormatters/index.ts +11 -3
  112. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +1 -1
  113. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +7 -3
  114. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +1 -1
  115. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
  116. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -1
  117. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +1 -1
  118. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +16 -5
  119. package/src/server/plugins/engine/pageControllers/{helpers.test.ts → helpers/helpers.test.ts} +2 -2
  120. package/src/server/plugins/engine/pageControllers/helpers/index.ts +20 -0
  121. package/src/server/plugins/engine/pageControllers/{helpers.ts → helpers/pages.ts} +1 -22
  122. package/src/server/plugins/engine/routes/index.ts +1 -1
  123. package/src/server/plugins/engine/routes/questions.test.ts +1 -1
  124. package/src/server/plugins/engine/routes/questions.ts +1 -1
  125. package/src/server/plugins/engine/services/notifyService.test.ts +156 -1
  126. package/src/server/plugins/engine/services/notifyService.ts +25 -4
  127. package/src/server/plugins/engine/types/index.ts +96 -0
  128. package/src/server/plugins/engine/types/schema.test.ts +152 -0
  129. package/src/server/plugins/engine/types/schema.ts +45 -0
  130. package/src/server/plugins/engine/types.ts +53 -3
  131. package/src/server/plugins/nunjucks/filters/answer.js +2 -2
  132. package/src/server/plugins/nunjucks/filters/answer.test.js +7 -4
  133. package/src/server/types.ts +2 -1
  134. package/.server/server/plugins/engine/components/helpers.js.map +0 -1
  135. package/.server/server/plugins/engine/pageControllers/helpers.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","server","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit } from '@hapi/hapi'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type FeaturedFormPageViewModel,\n type FileState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ uploadId }) => uploadId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const error = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n error,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const cacheService = getCacheService(request.server)\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","server","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit } from '@hapi/hapi'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type FeaturedFormPageViewModel,\n type FileState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ uploadId }) => uploadId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const error = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n error,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const cacheService = getCacheService(request.server)\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -2,7 +2,7 @@ import { hasComponentsEvenIfNoNext } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
3
  import { ComponentCollection } from "../components/ComponentCollection.js";
4
4
  import { FileUploadField } from "../components/FileUploadField.js";
5
- import { getAnswer } from "../components/helpers.js";
5
+ import { getAnswer } from "../components/helpers/components.js";
6
6
  import { checkEmailAddressForLiveFormSubmission, checkFormStatus, getCacheService } from "../helpers.js";
7
7
  import { SummaryViewModel } from "../models/index.js";
8
8
  import { QuestionPageController } from "./QuestionPageController.js";
@@ -77,9 +77,10 @@ export class SummaryPageController extends QuestionPageController {
77
77
  } = formsService;
78
78
 
79
79
  // Get the form metadata using the `slug` param
80
+ const formMetadata = await getFormMetadata(params.slug);
80
81
  const {
81
82
  notificationEmail
82
- } = await getFormMetadata(params.slug);
83
+ } = formMetadata;
83
84
  const {
84
85
  isPreview
85
86
  } = checkFormStatus(request.params);
@@ -89,7 +90,7 @@ export class SummaryPageController extends QuestionPageController {
89
90
  // Send submission email
90
91
  if (emailAddress) {
91
92
  const viewModel = this.getSummaryViewModel(request, context);
92
- await submitForm(context, request, viewModel, model, emailAddress);
93
+ await submitForm(context, request, viewModel, model, emailAddress, formMetadata);
93
94
  }
94
95
  await cacheService.setConfirmationState(request, {
95
96
  confirmed: true
@@ -112,7 +113,7 @@ export class SummaryPageController extends QuestionPageController {
112
113
  };
113
114
  }
114
115
  }
115
- async function submitForm(context, request, summaryViewModel, model, emailAddress) {
116
+ async function submitForm(context, request, summaryViewModel, model, emailAddress, formMetadata) {
116
117
  await extendFileRetention(model, context.state, emailAddress);
117
118
  const formStatus = checkFormStatus(request.params);
118
119
  const logTags = ['submit', 'submissionApi'];
@@ -127,7 +128,7 @@ async function submitForm(context, request, summaryViewModel, model, emailAddres
127
128
  if (submitResponse === undefined) {
128
129
  throw Boom.badRequest('Unexpected empty response from submit api');
129
130
  }
130
- return model.services.outputService.submit(context, request, model, emailAddress, items, submitResponse);
131
+ return model.services.outputService.submit(context, request, model, emailAddress, items, submitResponse, formMetadata);
131
132
  }
132
133
  async function extendFileRetention(model, state, updatedRetrievalKey) {
133
134
  const {
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","ComponentCollection","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","SummaryPageController","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","allowSaveAndReturn","shouldShowSaveAndReturn","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","params","cacheService","formsService","services","getFormMetadata","notificationEmail","slug","isPreview","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","state","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","fileUploadComponents","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndReturn = this.shouldShowSaveAndReturn(request.server)\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { model } = this\n const { params } = request\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const { notificationEmail } = await getFormMetadata(params.slug)\n const { isPreview } = checkFormStatus(request.params)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(context, request, viewModel, model, emailAddress)\n }\n\n await cacheService.setConfirmationState(request, { confirmed: true })\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nasync function submitForm(\n context: FormContext,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string\n) {\n await extendFileRetention(model, context.state, emailAddress)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","ComponentCollection","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","SummaryPageController","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","allowSaveAndReturn","shouldShowSaveAndReturn","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","state","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","fileUploadComponents","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndReturn = this.shouldShowSaveAndReturn(request.server)\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { model } = this\n const { params } = request\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(\n context,\n request,\n viewModel,\n model,\n emailAddress,\n formMetadata\n )\n }\n\n await cacheService.setConfirmationState(request, { confirmed: true })\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nasync function submitForm(\n context: FormContext,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await extendFileRetention(model, context.state, emailAddress)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * In local development environments, we sometimes need to rewrite the
3
+ * CDP upload URL to work with CSP/CORS restrictions.
4
+ * This helper function rewrites localhost URLs to use the sslip.io proxy
5
+ * This is only used when running locally with a development proxy.
6
+ * In non-local environments, this function returns null
7
+ * @param uploadUrl - The original upload URL from CDP
8
+ */
9
+ export declare function getProxyUrlForLocalDevelopment(uploadUrl?: string): string | null;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * In local development environments, we sometimes need to rewrite the
3
+ * CDP upload URL to work with CSP/CORS restrictions.
4
+ * This helper function rewrites localhost URLs to use the sslip.io proxy
5
+ * This is only used when running locally with a development proxy.
6
+ * In non-local environments, this function returns null
7
+ * @param uploadUrl - The original upload URL from CDP
8
+ */
9
+ export function getProxyUrlForLocalDevelopment(uploadUrl) {
10
+ if (!uploadUrl?.includes('localhost:7337')) {
11
+ return null;
12
+ }
13
+ return uploadUrl.replace(/localhost:7337/g, 'uploader.127.0.0.1.sslip.io:7300');
14
+ }
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["getProxyUrlForLocalDevelopment","uploadUrl","includes","replace"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/helpers/index.ts"],"sourcesContent":["/**\n * In local development environments, we sometimes need to rewrite the\n * CDP upload URL to work with CSP/CORS restrictions.\n * This helper function rewrites localhost URLs to use the sslip.io proxy\n * This is only used when running locally with a development proxy.\n * In non-local environments, this function returns null\n * @param uploadUrl - The original upload URL from CDP\n */\nexport function getProxyUrlForLocalDevelopment(\n uploadUrl?: string\n): string | null {\n if (!uploadUrl?.includes('localhost:7337')) {\n return null\n }\n\n return uploadUrl.replace(\n /localhost:7337/g,\n 'uploader.127.0.0.1.sslip.io:7300'\n )\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,8BAA8BA,CAC5CC,SAAkB,EACH;EACf,IAAI,CAACA,SAAS,EAAEC,QAAQ,CAAC,gBAAgB,CAAC,EAAE;IAC1C,OAAO,IAAI;EACb;EAEA,OAAOD,SAAS,CAACE,OAAO,CACtB,iBAAiB,EACjB,kCACF,CAAC;AACH","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { ControllerType, type Page } from '@defra/forms-model';
2
- import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js';
2
+ import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
3
3
  import * as PageControllers from '~/src/server/plugins/engine/pageControllers/index.js';
4
4
  export declare function isPageController(controllerName?: string | ControllerType): controllerName is keyof typeof PageControllers;
5
5
  export type PageControllerClass = InstanceType<PageControllerType>;
@@ -8,12 +8,3 @@ export type PageControllerType = (typeof PageControllers)[keyof typeof PageContr
8
8
  * Creates page instance for each {@link Page} type
9
9
  */
10
10
  export declare function createPage(model: FormModel, pageDef: Page): PageControllerClass;
11
- /**
12
- * In local development environments, we sometimes need to rewrite the
13
- * CDP upload URL to work with CSP/CORS restrictions.
14
- * This helper function rewrites localhost URLs to use the sslip.io proxy
15
- * This is only used when running locally with a development proxy.
16
- * In non-local environments, this function returns null
17
- * @param uploadUrl - The original upload URL from CDP
18
- */
19
- export declare function getProxyUrlForLocalDevelopment(uploadUrl?: string): string | null;
@@ -1,5 +1,5 @@
1
1
  import { ControllerType, controllerNameFromPath, isControllerName } from '@defra/forms-model';
2
- import * as PageControllers from "./index.js";
2
+ import * as PageControllers from "../index.js";
3
3
  export function isPageController(controllerName) {
4
4
  return isControllerName(controllerName) && controllerName in PageControllers;
5
5
  }
@@ -51,19 +51,4 @@ export function createPage(model, pageDef) {
51
51
  }
52
52
  return controller;
53
53
  }
54
-
55
- /**
56
- * In local development environments, we sometimes need to rewrite the
57
- * CDP upload URL to work with CSP/CORS restrictions.
58
- * This helper function rewrites localhost URLs to use the sslip.io proxy
59
- * This is only used when running locally with a development proxy.
60
- * In non-local environments, this function returns null
61
- * @param uploadUrl - The original upload URL from CDP
62
- */
63
- export function getProxyUrlForLocalDevelopment(uploadUrl) {
64
- if (!uploadUrl?.includes('localhost:7337')) {
65
- return null;
66
- }
67
- return uploadUrl.replace(/localhost:7337/g, 'uploader.127.0.0.1.sslip.io:7300');
68
- }
69
- //# sourceMappingURL=helpers.js.map
54
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","names":["ControllerType","controllerNameFromPath","isControllerName","PageControllers","isPageController","controllerName","createPage","model","pageDef","controller","QuestionPageController","Start","StartPageController","Page","Terminal","TerminalPageController","Summary","SummaryPageController","Status","StatusPageController","FileUpload","FileUploadPageController","Repeat","RepeatPageController","controllers","Ctrl","Error"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/helpers/pages.ts"],"sourcesContent":["import {\n ControllerType,\n controllerNameFromPath,\n isControllerName,\n type Page\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport * as PageControllers from '~/src/server/plugins/engine/pageControllers/index.js'\n\nexport function isPageController(\n controllerName?: string | ControllerType\n): controllerName is keyof typeof PageControllers {\n return isControllerName(controllerName) && controllerName in PageControllers\n}\n\nexport type PageControllerClass = InstanceType<PageControllerType>\nexport type PageControllerType =\n (typeof PageControllers)[keyof typeof PageControllers]\n\n/**\n * Creates page instance for each {@link Page} type\n */\nexport function createPage(model: FormModel, pageDef: Page) {\n const controllerName = controllerNameFromPath(pageDef.controller)\n\n if (!pageDef.controller) {\n return new PageControllers.QuestionPageController(model, pageDef)\n }\n\n // Patch legacy controllers\n if (controllerName && pageDef.controller !== controllerName) {\n pageDef.controller = controllerName\n }\n\n let controller: PageControllerClass | undefined\n\n switch (pageDef.controller) {\n case ControllerType.Start:\n controller = new PageControllers.StartPageController(model, pageDef)\n break\n\n case ControllerType.Page:\n controller = new PageControllers.QuestionPageController(model, pageDef)\n break\n\n case ControllerType.Terminal:\n controller = new PageControllers.TerminalPageController(model, pageDef)\n break\n\n case ControllerType.Summary:\n controller = new PageControllers.SummaryPageController(model, pageDef)\n break\n\n case ControllerType.Status:\n controller = new PageControllers.StatusPageController(model, pageDef)\n break\n\n case ControllerType.FileUpload:\n controller = new PageControllers.FileUploadPageController(model, pageDef)\n break\n\n case ControllerType.Repeat:\n controller = new PageControllers.RepeatPageController(model, pageDef)\n break\n }\n\n if (typeof controller === 'undefined') {\n if (model.controllers?.[pageDef.controller]) {\n const Ctrl = model.controllers[pageDef.controller]\n controller = new Ctrl(model, pageDef) as unknown as PageControllerClass // type assertion needed because TS can't verify custom controller structure\n }\n }\n\n if (typeof controller === 'undefined') {\n throw new Error(`Page controller ${pageDef.controller} does not exist`)\n }\n\n return controller\n}\n"],"mappings":"AAAA,SACEA,cAAc,EACdC,sBAAsB,EACtBC,gBAAgB,QAEX,oBAAoB;AAG3B,OAAO,KAAKC,eAAe;AAE3B,OAAO,SAASC,gBAAgBA,CAC9BC,cAAwC,EACQ;EAChD,OAAOH,gBAAgB,CAACG,cAAc,CAAC,IAAIA,cAAc,IAAIF,eAAe;AAC9E;AAMA;AACA;AACA;AACA,OAAO,SAASG,UAAUA,CAACC,KAAgB,EAAEC,OAAa,EAAE;EAC1D,MAAMH,cAAc,GAAGJ,sBAAsB,CAACO,OAAO,CAACC,UAAU,CAAC;EAEjE,IAAI,CAACD,OAAO,CAACC,UAAU,EAAE;IACvB,OAAO,IAAIN,eAAe,CAACO,sBAAsB,CAACH,KAAK,EAAEC,OAAO,CAAC;EACnE;;EAEA;EACA,IAAIH,cAAc,IAAIG,OAAO,CAACC,UAAU,KAAKJ,cAAc,EAAE;IAC3DG,OAAO,CAACC,UAAU,GAAGJ,cAAc;EACrC;EAEA,IAAII,UAA2C;EAE/C,QAAQD,OAAO,CAACC,UAAU;IACxB,KAAKT,cAAc,CAACW,KAAK;MACvBF,UAAU,GAAG,IAAIN,eAAe,CAACS,mBAAmB,CAACL,KAAK,EAAEC,OAAO,CAAC;MACpE;IAEF,KAAKR,cAAc,CAACa,IAAI;MACtBJ,UAAU,GAAG,IAAIN,eAAe,CAACO,sBAAsB,CAACH,KAAK,EAAEC,OAAO,CAAC;MACvE;IAEF,KAAKR,cAAc,CAACc,QAAQ;MAC1BL,UAAU,GAAG,IAAIN,eAAe,CAACY,sBAAsB,CAACR,KAAK,EAAEC,OAAO,CAAC;MACvE;IAEF,KAAKR,cAAc,CAACgB,OAAO;MACzBP,UAAU,GAAG,IAAIN,eAAe,CAACc,qBAAqB,CAACV,KAAK,EAAEC,OAAO,CAAC;MACtE;IAEF,KAAKR,cAAc,CAACkB,MAAM;MACxBT,UAAU,GAAG,IAAIN,eAAe,CAACgB,oBAAoB,CAACZ,KAAK,EAAEC,OAAO,CAAC;MACrE;IAEF,KAAKR,cAAc,CAACoB,UAAU;MAC5BX,UAAU,GAAG,IAAIN,eAAe,CAACkB,wBAAwB,CAACd,KAAK,EAAEC,OAAO,CAAC;MACzE;IAEF,KAAKR,cAAc,CAACsB,MAAM;MACxBb,UAAU,GAAG,IAAIN,eAAe,CAACoB,oBAAoB,CAAChB,KAAK,EAAEC,OAAO,CAAC;MACrE;EACJ;EAEA,IAAI,OAAOC,UAAU,KAAK,WAAW,EAAE;IACrC,IAAIF,KAAK,CAACiB,WAAW,GAAGhB,OAAO,CAACC,UAAU,CAAC,EAAE;MAC3C,MAAMgB,IAAI,GAAGlB,KAAK,CAACiB,WAAW,CAAChB,OAAO,CAACC,UAAU,CAAC;MAClDA,UAAU,GAAG,IAAIgB,IAAI,CAAClB,KAAK,EAAEC,OAAO,CAAmC,EAAC;IAC1E;EACF;EAEA,IAAI,OAAOC,UAAU,KAAK,WAAW,EAAE;IACrC,MAAM,IAAIiB,KAAK,CAAC,mBAAmBlB,OAAO,CAACC,UAAU,iBAAiB,CAAC;EACzE;EAEA,OAAOA,UAAU;AACnB","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { type ResponseObject, type ResponseToolkit, type Server } from '@hapi/hapi';
2
- import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js';
2
+ import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
3
3
  import { type FormContext, type PluginOptions } from '~/src/server/plugins/engine/types.js';
4
4
  import { type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
5
5
  export declare function redirectOrMakeHandler(request: FormRequest | FormRequestPayload, h: Pick<ResponseToolkit, 'redirect' | 'view'>, makeHandler: (page: PageControllerClass, context: FormContext) => ResponseObject | Promise<ResponseObject>): Promise<ResponseObject>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Boom","isEqual","PREVIEW_PATH_PREFIX","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","FormModel","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","onRequest","formsService","handler","continue","slug","isPreview","formState","getFormMetadata","id","key","item","models","get","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","set","dispatchHandler","servicePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\nimport { isEqual } from 'date-fns'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: FormRequest | FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const { services = defaultServices, controllers, onRequest } = options\n\n const { formsService } = services\n\n async function handler(\n request: FormRequest | FormRequestPayload,\n h: ResponseToolkit\n ) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n // Get the form metadata using the `slug` param\n const metadata = await formsService.getFormMetadata(slug)\n\n const { id, [formState]: state } = metadata\n\n // Check the metadata supports the requested state\n if (!state) {\n throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)\n }\n\n // Cache the models based on id, state and whether\n // it's a preview or not. There could be up to 3 models\n // cached for a single form:\n // \"{id}_live_false\" (live/live)\n // \"{id}_live_true\" (live/preview)\n // \"{id}_draft_true\" (draft/preview)\n const key = `${id}_${formState}_${isPreview}`\n let item = server.app.models.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)\n\n // Get the form definition using the `id` from the metadata\n const definition = await formsService.getFormDefinition(id, formState)\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${id} (${slug}) ${formState}`\n )\n }\n\n const emailAddress = metadata.notificationEmail ?? definition.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Build the form model\n server.logger.info(\n `Building model for form definition ${id} (${slug}) ${formState}`\n )\n\n // Set up the basePath for the model\n const basePath = (\n isPreview\n ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`\n : `${prefix}/${slug}`\n ).substring(1)\n\n // Construct the form model\n const model = new FormModel(\n definition,\n { basePath },\n services,\n controllers\n )\n\n // Create new item and add it to the item cache\n item = { model, updatedAt: state.updatedAt }\n server.app.models.set(key, item)\n }\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n onRequest(request, params, item.model.def, metadata)\n }\n\n // Assign the model to the request data\n // for use in the downstream handler\n request.app.model = item.model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"AAAA,OAAOA,IAAI,MAAM,YAAY;AAM7B,SAASC,OAAO,QAAQ,UAAU;AAElC,SAASC,mBAAmB;AAC5B,SACEC,sCAAsC,EACtCC,eAAe,EACfC,QAAQ,EACRC,eAAe,EACfC,OAAO,EACPC,YAAY,EACZC,OAAO;AAET,SAASC,SAAS;AAElB,SAASC,uBAAuB;AAChC,OAAO,KAAKC,eAAe;AAU3B,OAAO,eAAeC,qBAAqBA,CACzCC,OAAyC,EACzCC,CAA6C,EAC7CC,WAG6C,EAC7C;EACA,MAAM;IAAEC,GAAG;IAAEC;EAAO,CAAC,GAAGJ,OAAO;EAC/B,MAAM;IAAEK;EAAM,CAAC,GAAGF,GAAG;EAErB,IAAI,CAACE,KAAK,EAAE;IACV,MAAMnB,IAAI,CAACoB,QAAQ,CAAC,uBAAuBF,MAAM,CAACG,IAAI,EAAE,CAAC;EAC3D;EAEA,MAAMC,YAAY,GAAGhB,eAAe,CAACQ,OAAO,CAACS,MAAM,CAAC;EACpD,MAAMC,IAAI,GAAGjB,OAAO,CAACY,KAAK,EAAEL,OAAO,CAAC;EACpC,IAAIW,KAAK,GAAG,MAAMD,IAAI,CAACE,QAAQ,CAACZ,OAAO,CAAC;EAExC,IAAI,CAACW,KAAK,CAACE,mBAAmB,EAAE;IAC9B,MAAMC,MAAM,GAAGT,KAAK,CAACU,GAAG,CAACC,QAAQ,EAAEC,qBAAqB,IAAI,EAAE;IAE9D,IAAI,OAAOH,MAAM,KAAK,QAAQ,EAAE;MAC9B,MAAM5B,IAAI,CAACgC,iBAAiB,CAC1B,uDACF,CAAC;IACH;IAEA,MAAMC,eAAe,GAAGtB,uBAAuB,CAACiB,MAAM,CAAC;IACvDH,KAAK,GAAG,MAAMD,IAAI,CAACU,UAAU,CAACpB,OAAO,EAAEW,KAAK,EAAE;MAC5CE,mBAAmB,EAAEM;IACvB,CAAC,CAAC;EACJ;EAEA,MAAME,KAAK,GAAGb,YAAY,CAACc,QAAQ,CAACtB,OAAO,CAAC;EAC5C,MAAMuB,OAAO,GAAGlB,KAAK,CAACmB,cAAc,CAACxB,OAAO,EAAEW,KAAK,EAAEU,KAAK,EAAEI,MAAM,CAAC;EACnE,MAAMC,YAAY,GAAGhB,IAAI,CAACiB,eAAe,CAAC3B,OAAO,EAAEuB,OAAO,CAAC;EAC3D,MAAMK,WAAW,GAAGlB,IAAI,CAACmB,cAAc,CAAC,CAAC;;EAEzC;EACA,IAAIH,YAAY,CAACI,UAAU,CAACpB,IAAI,CAACH,IAAI,CAAC,IAAIgB,OAAO,CAACQ,aAAa,EAAE;IAC/D,OAAO7B,WAAW,CAACQ,IAAI,EAAEa,OAAO,CAAC;EACnC;;EAEA;EACA,MAAMS,UAAU,GAAGzC,QAAQ,CAACc,KAAK,EAAEqB,YAAY,CAAC;;EAEhD;EACA,IAAIM,UAAU,EAAEC,IAAI,CAACC,MAAM,EAAE;IAC3BlC,OAAO,CAACmC,KAAK,CAACC,SAAS,GAAG1B,IAAI,CAAC2B,OAAO,CAACT,WAAW,CAAC;EACrD;EAEA,OAAOjC,OAAO,CAACK,OAAO,EAAEC,CAAC,EAAES,IAAI,CAAC2B,OAAO,CAACX,YAAY,CAAC,CAAC;AACxD;AAEA,OAAO,SAASY,sBAAsBA,CAAC7B,MAAc,EAAE8B,OAAsB,EAAE;EAC7E;EACA,MAAMzB,MAAM,GAAGL,MAAM,CAAC+B,KAAK,CAACC,SAAS,CAACC,KAAK,CAAC5B,MAAM,IAAI,EAAE;EAExD,MAAM;IAAE6B,QAAQ,GAAG7C,eAAe;IAAE8C,WAAW;IAAEC;EAAU,CAAC,GAAGN,OAAO;EAEtE,MAAM;IAAEO;EAAa,CAAC,GAAGH,QAAQ;EAEjC,eAAeI,OAAOA,CACpB/C,OAAyC,EACzCC,CAAkB,EAClB;IACA,IAAIQ,MAAM,CAACN,GAAG,CAACE,KAAK,EAAE;MACpBL,OAAO,CAACG,GAAG,CAACE,KAAK,GAAGI,MAAM,CAACN,GAAG,CAACE,KAAK;MAEpC,OAAOJ,CAAC,CAAC+C,QAAQ;IACnB;IAEA,MAAM;MAAE5C;IAAO,CAAC,GAAGJ,OAAO;IAC1B,MAAM;MAAEiD;IAAK,CAAC,GAAG7C,MAAM;IACvB,MAAM;MAAE8C,SAAS;MAAEvC,KAAK,EAAEwC;IAAU,CAAC,GAAG7D,eAAe,CAACc,MAAM,CAAC;;IAE/D;IACA,MAAMY,QAAQ,GAAG,MAAM8B,YAAY,CAACM,eAAe,CAACH,IAAI,CAAC;IAEzD,MAAM;MAAEI,EAAE;MAAE,CAACF,SAAS,GAAGxC;IAAM,CAAC,GAAGK,QAAQ;;IAE3C;IACA,IAAI,CAACL,KAAK,EAAE;MACV,MAAMzB,IAAI,CAACoB,QAAQ,CAAC,OAAO6C,SAAS,6BAA6BE,EAAE,EAAE,CAAC;IACxE;;IAEA;IACA;IACA;IACA;IACA;IACA;IACA,MAAMC,GAAG,GAAG,GAAGD,EAAE,IAAIF,SAAS,IAAID,SAAS,EAAE;IAC7C,IAAIK,IAAI,GAAG9C,MAAM,CAACN,GAAG,CAACqD,MAAM,CAACC,GAAG,CAACH,GAAG,CAAC;IAErC,IAAI,CAACC,IAAI,IAAI,CAACpE,OAAO,CAACoE,IAAI,CAACG,SAAS,EAAE/C,KAAK,CAAC+C,SAAS,CAAC,EAAE;MACtDjD,MAAM,CAACkD,MAAM,CAACC,IAAI,CAAC,2BAA2BP,EAAE,KAAKJ,IAAI,KAAKE,SAAS,EAAE,CAAC;;MAE1E;MACA,MAAMU,UAAU,GAAG,MAAMf,YAAY,CAACgB,iBAAiB,CAACT,EAAE,EAAEF,SAAS,CAAC;MAEtE,IAAI,CAACU,UAAU,EAAE;QACf,MAAM3E,IAAI,CAACoB,QAAQ,CACjB,yCAAyC+C,EAAE,KAAKJ,IAAI,KAAKE,SAAS,EACpE,CAAC;MACH;MAEA,MAAMY,YAAY,GAAG/C,QAAQ,CAACgD,iBAAiB,IAAIH,UAAU,CAACI,WAAW;MAEzE5E,sCAAsC,CAAC0E,YAAY,EAAEb,SAAS,CAAC;;MAE/D;MACAzC,MAAM,CAACkD,MAAM,CAACC,IAAI,CAChB,sCAAsCP,EAAE,KAAKJ,IAAI,KAAKE,SAAS,EACjE,CAAC;;MAED;MACA,MAAMe,QAAQ,GAAG,CACfhB,SAAS,GACL,GAAGpC,MAAM,GAAG1B,mBAAmB,IAAI+D,SAAS,IAAIF,IAAI,EAAE,GACtD,GAAGnC,MAAM,IAAImC,IAAI,EAAE,EACvBkB,SAAS,CAAC,CAAC,CAAC;;MAEd;MACA,MAAM9D,KAAK,GAAG,IAAIT,SAAS,CACzBiE,UAAU,EACV;QAAEK;MAAS,CAAC,EACZvB,QAAQ,EACRC,WACF,CAAC;;MAED;MACAW,IAAI,GAAG;QAAElD,KAAK;QAAEqD,SAAS,EAAE/C,KAAK,CAAC+C;MAAU,CAAC;MAC5CjD,MAAM,CAACN,GAAG,CAACqD,MAAM,CAACY,GAAG,CAACd,GAAG,EAAEC,IAAI,CAAC;IAClC;;IAEA;IACA,IAAIV,SAAS,EAAE;MACbA,SAAS,CAAC7C,OAAO,EAAEI,MAAM,EAAEmD,IAAI,CAAClD,KAAK,CAACU,GAAG,EAAEC,QAAQ,CAAC;IACtD;;IAEA;IACA;IACAhB,OAAO,CAACG,GAAG,CAACE,KAAK,GAAGkD,IAAI,CAAClD,KAAK;IAE9B,OAAOJ,CAAC,CAAC+C,QAAQ;EACnB;EAEA,OAAOD,OAAO;AAChB;AAEA,OAAO,SAASsB,eAAeA,CAC7BrE,OAAoB,EACpBC,CAA6C,EAC7C;EACA,MAAM;IAAEI;EAAM,CAAC,GAAGL,OAAO,CAACG,GAAG;EAE7B,MAAMmE,WAAW,GAAGjE,KAAK,GAAG,IAAIA,KAAK,CAAC6D,QAAQ,EAAE,GAAG,EAAE;EACrD,OAAOvE,OAAO,CAACK,OAAO,EAAEC,CAAC,EAAE,GAAGqE,WAAW,GAAG5E,YAAY,CAACW,KAAK,CAAC,EAAE,CAAC;AACpE","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["Boom","isEqual","PREVIEW_PATH_PREFIX","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","FormModel","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","onRequest","formsService","handler","continue","slug","isPreview","formState","getFormMetadata","id","key","item","models","get","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","set","dispatchHandler","servicePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\nimport { isEqual } from 'date-fns'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: FormRequest | FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const { services = defaultServices, controllers, onRequest } = options\n\n const { formsService } = services\n\n async function handler(\n request: FormRequest | FormRequestPayload,\n h: ResponseToolkit\n ) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n // Get the form metadata using the `slug` param\n const metadata = await formsService.getFormMetadata(slug)\n\n const { id, [formState]: state } = metadata\n\n // Check the metadata supports the requested state\n if (!state) {\n throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)\n }\n\n // Cache the models based on id, state and whether\n // it's a preview or not. There could be up to 3 models\n // cached for a single form:\n // \"{id}_live_false\" (live/live)\n // \"{id}_live_true\" (live/preview)\n // \"{id}_draft_true\" (draft/preview)\n const key = `${id}_${formState}_${isPreview}`\n let item = server.app.models.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)\n\n // Get the form definition using the `id` from the metadata\n const definition = await formsService.getFormDefinition(id, formState)\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${id} (${slug}) ${formState}`\n )\n }\n\n const emailAddress = metadata.notificationEmail ?? definition.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Build the form model\n server.logger.info(\n `Building model for form definition ${id} (${slug}) ${formState}`\n )\n\n // Set up the basePath for the model\n const basePath = (\n isPreview\n ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`\n : `${prefix}/${slug}`\n ).substring(1)\n\n // Construct the form model\n const model = new FormModel(\n definition,\n { basePath },\n services,\n controllers\n )\n\n // Create new item and add it to the item cache\n item = { model, updatedAt: state.updatedAt }\n server.app.models.set(key, item)\n }\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n onRequest(request, params, item.model.def, metadata)\n }\n\n // Assign the model to the request data\n // for use in the downstream handler\n request.app.model = item.model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","handleHttpEvent","request","page","context","event","model","preparePageEventRequestOptions","options","url","viewModel","items","details","payload","undefined","opts","response","postJson","Object","assign","data","makeGetHandler","getHandler","h","params","path","events","app","notFound","onLoad","type","makeGetRouteHandler","makePostHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","onSave","isSuccessful","statusCode","isBoom","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type FormContext,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nasync function handleHttpEvent(\n request: FormRequest | FormRequestPayload,\n page: PageControllerClass,\n context: FormContext,\n event: Event,\n model: FormModel,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n const { options } = event\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(context, items, model, undefined, undefined)\n const opts = { payload }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, event, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n}\n\nexport function makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n return function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(request, h, async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad && events.onLoad.type === 'http') {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onLoad,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n })\n }\n}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(request, h, async (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n const { model } = request.app\n const { events } = page\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n if (!model) {\n throw Boom.notFound(`No model found for /${request.params.path}`)\n }\n\n const response = await page.makePostRouteHandler()(request, context, h)\n\n if (\n events?.onSave &&\n events.onSave.type === 'http' &&\n isSuccessful(response)\n ) {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onSave,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return response\n })\n }\n}\n\nfunction isSuccessful(response: ResponseObject): boolean {\n const { statusCode } = response\n\n return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","handleHttpEvent","request","page","context","event","model","preparePageEventRequestOptions","options","url","viewModel","items","details","payload","undefined","opts","response","postJson","Object","assign","data","makeGetHandler","getHandler","h","params","path","events","app","notFound","onLoad","type","makeGetRouteHandler","makePostHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","onSave","isSuccessful","statusCode","isBoom","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type FormContext,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nasync function handleHttpEvent(\n request: FormRequest | FormRequestPayload,\n page: PageControllerClass,\n context: FormContext,\n event: Event,\n model: FormModel,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n const { options } = event\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(context, items, model, undefined, undefined)\n const opts = { payload }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, event, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n}\n\nexport function makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n return function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(request, h, async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad && events.onLoad.type === 'http') {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onLoad,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n })\n }\n}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(request, h, async (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n const { model } = request.app\n const { events } = page\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n if (!model) {\n throw Boom.notFound(`No model found for /${request.params.path}`)\n }\n\n const response = await page.makePostRouteHandler()(request, context, h)\n\n if (\n events?.onSave &&\n events.onSave.type === 'http' &&\n isSuccessful(response)\n ) {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onSave,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return response\n })\n }\n}\n\nfunction isSuccessful(response: ResponseObject): boolean {\n const { statusCode } = response\n\n return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- export function formsService(): Promise<import("../../../types.js").FormsService>;
1
+ export function formsService(): Promise<import("../types/index.js").FormsService>;
@@ -1,6 +1,11 @@
1
- import { type SubmitResponsePayload } from '@defra/forms-model';
1
+ import { type FormMetadata, type SubmitResponsePayload } from '@defra/forms-model';
2
2
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
3
3
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
4
4
  import { type FormContext } from '~/src/server/plugins/engine/types.js';
5
5
  import { type FormRequestPayload } from '~/src/server/routes/types.js';
6
- export declare function submit(context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload): Promise<void>;
6
+ /**
7
+ * Optional GOV.UK Notify service for consumers who want email notifications
8
+ * Can be disabled by not providing notifyTemplateId in config
9
+ * Can be overridden by providing a custom outputService in the services config
10
+ */
11
+ export declare function submit(context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, formMetadata?: FormMetadata): Promise<void>;
@@ -1,11 +1,20 @@
1
1
  import { getErrorMessage } from '@defra/forms-model';
2
2
  import { config } from "../../../../config/index.js";
3
- import { escapeMarkdown } from "../components/helpers.js";
3
+ import { escapeMarkdown } from "../components/helpers/index.js";
4
4
  import { checkFormStatus } from "../helpers.js";
5
5
  import { getFormatter } from "../outputFormatters/index.js";
6
6
  import { sendNotification } from "../../../utils/notify.js";
7
7
  const templateId = config.get('notifyTemplateId');
8
- export async function submit(context, request, model, emailAddress, items, submitResponse) {
8
+
9
+ /**
10
+ * Optional GOV.UK Notify service for consumers who want email notifications
11
+ * Can be disabled by not providing notifyTemplateId in config
12
+ * Can be overridden by providing a custom outputService in the services config
13
+ */
14
+ export async function submit(context, request, model, emailAddress, items, submitResponse, formMetadata) {
15
+ if (!templateId) {
16
+ return Promise.resolve();
17
+ }
9
18
  const logTags = ['submit', 'email'];
10
19
  const formStatus = checkFormStatus(request.params);
11
20
 
@@ -16,7 +25,7 @@ export async function submit(context, request, model, emailAddress, items, submi
16
25
  const outputAudience = model.def.output?.audience ?? 'human';
17
26
  const outputVersion = model.def.output?.version ?? '1';
18
27
  const outputFormatter = getFormatter(outputAudience, outputVersion);
19
- let body = outputFormatter(context, items, model, submitResponse, formStatus);
28
+ let body = outputFormatter(context, items, model, submitResponse, formStatus, formMetadata);
20
29
 
21
30
  // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload
22
31
  // This is logic specific to Notify, so we include the logic here rather than in the formatter
@@ -1 +1 @@
1
- {"version":3,"file":"notifyService.js","names":["getErrorMessage","config","escapeMarkdown","checkFormStatus","getFormatter","sendNotification","templateId","get","submit","context","request","model","emailAddress","items","submitResponse","logTags","formStatus","params","logger","info","formName","name","subject","isPreview","outputAudience","def","output","audience","outputVersion","version","outputFormatter","body","Buffer","from","toString","personalisation","err","errMsg","error"],"sources":["../../../../../src/server/plugins/engine/services/notifyService.ts"],"sourcesContent":["import { getErrorMessage, type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers.js'\nimport { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { getFormatter } from '~/src/server/plugins/engine/outputFormatters/index.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequestPayload } from '~/src/server/routes/types.js'\nimport { sendNotification } from '~/src/server/utils/notify.js'\n\nconst templateId = config.get('notifyTemplateId')\n\nexport async function submit(\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n) {\n const logTags = ['submit', 'email']\n const formStatus = checkFormStatus(request.params)\n\n // Get submission email personalisation\n request.logger.info(logTags, 'Getting personalisation data')\n\n const formName = escapeMarkdown(model.name)\n const subject = formStatus.isPreview\n ? `TEST FORM SUBMISSION: ${formName}`\n : `Form submission: ${formName}`\n\n const outputAudience = model.def.output?.audience ?? 'human'\n const outputVersion = model.def.output?.version ?? '1'\n\n const outputFormatter = getFormatter(outputAudience, outputVersion)\n let body = outputFormatter(context, items, model, submitResponse, formStatus)\n\n // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload\n // This is logic specific to Notify, so we include the logic here rather than in the formatter\n if (outputAudience === 'machine') {\n body = Buffer.from(body).toString('base64')\n }\n\n request.logger.info(logTags, 'Sending email')\n\n try {\n // Send submission email\n await sendNotification({\n templateId,\n emailAddress,\n personalisation: {\n subject,\n body\n }\n })\n\n request.logger.info(logTags, 'Email sent successfully')\n } catch (err) {\n const errMsg = getErrorMessage(err)\n request.logger.error(\n errMsg,\n `[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${errMsg}`\n )\n\n throw err\n }\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAoC,oBAAoB;AAEhF,SAASC,MAAM;AACf,SAASC,cAAc;AACvB,SAASC,eAAe;AAGxB,SAASC,YAAY;AAGrB,SAASC,gBAAgB;AAEzB,MAAMC,UAAU,GAAGL,MAAM,CAACM,GAAG,CAAC,kBAAkB,CAAC;AAEjD,OAAO,eAAeC,MAAMA,CAC1BC,OAAoB,EACpBC,OAA2B,EAC3BC,KAAgB,EAChBC,YAAoB,EACpBC,KAAmB,EACnBC,cAAqC,EACrC;EACA,MAAMC,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC;EACnC,MAAMC,UAAU,GAAGb,eAAe,CAACO,OAAO,CAACO,MAAM,CAAC;;EAElD;EACAP,OAAO,CAACQ,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,8BAA8B,CAAC;EAE5D,MAAMK,QAAQ,GAAGlB,cAAc,CAACS,KAAK,CAACU,IAAI,CAAC;EAC3C,MAAMC,OAAO,GAAGN,UAAU,CAACO,SAAS,GAChC,yBAAyBH,QAAQ,EAAE,GACnC,oBAAoBA,QAAQ,EAAE;EAElC,MAAMI,cAAc,GAAGb,KAAK,CAACc,GAAG,CAACC,MAAM,EAAEC,QAAQ,IAAI,OAAO;EAC5D,MAAMC,aAAa,GAAGjB,KAAK,CAACc,GAAG,CAACC,MAAM,EAAEG,OAAO,IAAI,GAAG;EAEtD,MAAMC,eAAe,GAAG1B,YAAY,CAACoB,cAAc,EAAEI,aAAa,CAAC;EACnE,IAAIG,IAAI,GAAGD,eAAe,CAACrB,OAAO,EAAEI,KAAK,EAAEF,KAAK,EAAEG,cAAc,EAAEE,UAAU,CAAC;;EAE7E;EACA;EACA,IAAIQ,cAAc,KAAK,SAAS,EAAE;IAChCO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACF,IAAI,CAAC,CAACG,QAAQ,CAAC,QAAQ,CAAC;EAC7C;EAEAxB,OAAO,CAACQ,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,eAAe,CAAC;EAE7C,IAAI;IACF;IACA,MAAMV,gBAAgB,CAAC;MACrBC,UAAU;MACVM,YAAY;MACZuB,eAAe,EAAE;QACfb,OAAO;QACPS;MACF;IACF,CAAC,CAAC;IAEFrB,OAAO,CAACQ,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,yBAAyB,CAAC;EACzD,CAAC,CAAC,OAAOqB,GAAG,EAAE;IACZ,MAAMC,MAAM,GAAGrC,eAAe,CAACoC,GAAG,CAAC;IACnC1B,OAAO,CAACQ,MAAM,CAACoB,KAAK,CAClBD,MAAM,EACN,oEAAoE/B,UAAU,iBAAiBM,YAAY,MAAMyB,MAAM,EACzH,CAAC;IAED,MAAMD,GAAG;EACX;AACF","ignoreList":[]}
1
+ {"version":3,"file":"notifyService.js","names":["getErrorMessage","config","escapeMarkdown","checkFormStatus","getFormatter","sendNotification","templateId","get","submit","context","request","model","emailAddress","items","submitResponse","formMetadata","Promise","resolve","logTags","formStatus","params","logger","info","formName","name","subject","isPreview","outputAudience","def","output","audience","outputVersion","version","outputFormatter","body","Buffer","from","toString","personalisation","err","errMsg","error"],"sources":["../../../../../src/server/plugins/engine/services/notifyService.ts"],"sourcesContent":["import {\n getErrorMessage,\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { getFormatter } from '~/src/server/plugins/engine/outputFormatters/index.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequestPayload } from '~/src/server/routes/types.js'\nimport { sendNotification } from '~/src/server/utils/notify.js'\n\nconst templateId = config.get('notifyTemplateId')\n\n/**\n * Optional GOV.UK Notify service for consumers who want email notifications\n * Can be disabled by not providing notifyTemplateId in config\n * Can be overridden by providing a custom outputService in the services config\n */\nexport async function submit(\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n) {\n if (!templateId) {\n return Promise.resolve()\n }\n\n const logTags = ['submit', 'email']\n const formStatus = checkFormStatus(request.params)\n\n // Get submission email personalisation\n request.logger.info(logTags, 'Getting personalisation data')\n\n const formName = escapeMarkdown(model.name)\n const subject = formStatus.isPreview\n ? `TEST FORM SUBMISSION: ${formName}`\n : `Form submission: ${formName}`\n\n const outputAudience = model.def.output?.audience ?? 'human'\n const outputVersion = model.def.output?.version ?? '1'\n\n const outputFormatter = getFormatter(outputAudience, outputVersion)\n let body = outputFormatter(\n context,\n items,\n model,\n submitResponse,\n formStatus,\n formMetadata\n )\n\n // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload\n // This is logic specific to Notify, so we include the logic here rather than in the formatter\n if (outputAudience === 'machine') {\n body = Buffer.from(body).toString('base64')\n }\n\n request.logger.info(logTags, 'Sending email')\n\n try {\n // Send submission email\n await sendNotification({\n templateId,\n emailAddress,\n personalisation: {\n subject,\n body\n }\n })\n\n request.logger.info(logTags, 'Email sent successfully')\n } catch (err) {\n const errMsg = getErrorMessage(err)\n request.logger.error(\n errMsg,\n `[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${errMsg}`\n )\n\n throw err\n }\n}\n"],"mappings":"AAAA,SACEA,eAAe,QAGV,oBAAoB;AAE3B,SAASC,MAAM;AACf,SAASC,cAAc;AACvB,SAASC,eAAe;AAGxB,SAASC,YAAY;AAGrB,SAASC,gBAAgB;AAEzB,MAAMC,UAAU,GAAGL,MAAM,CAACM,GAAG,CAAC,kBAAkB,CAAC;;AAEjD;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,MAAMA,CAC1BC,OAAoB,EACpBC,OAA2B,EAC3BC,KAAgB,EAChBC,YAAoB,EACpBC,KAAmB,EACnBC,cAAqC,EACrCC,YAA2B,EAC3B;EACA,IAAI,CAACT,UAAU,EAAE;IACf,OAAOU,OAAO,CAACC,OAAO,CAAC,CAAC;EAC1B;EAEA,MAAMC,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC;EACnC,MAAMC,UAAU,GAAGhB,eAAe,CAACO,OAAO,CAACU,MAAM,CAAC;;EAElD;EACAV,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,8BAA8B,CAAC;EAE5D,MAAMK,QAAQ,GAAGrB,cAAc,CAACS,KAAK,CAACa,IAAI,CAAC;EAC3C,MAAMC,OAAO,GAAGN,UAAU,CAACO,SAAS,GAChC,yBAAyBH,QAAQ,EAAE,GACnC,oBAAoBA,QAAQ,EAAE;EAElC,MAAMI,cAAc,GAAGhB,KAAK,CAACiB,GAAG,CAACC,MAAM,EAAEC,QAAQ,IAAI,OAAO;EAC5D,MAAMC,aAAa,GAAGpB,KAAK,CAACiB,GAAG,CAACC,MAAM,EAAEG,OAAO,IAAI,GAAG;EAEtD,MAAMC,eAAe,GAAG7B,YAAY,CAACuB,cAAc,EAAEI,aAAa,CAAC;EACnE,IAAIG,IAAI,GAAGD,eAAe,CACxBxB,OAAO,EACPI,KAAK,EACLF,KAAK,EACLG,cAAc,EACdK,UAAU,EACVJ,YACF,CAAC;;EAED;EACA;EACA,IAAIY,cAAc,KAAK,SAAS,EAAE;IAChCO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACF,IAAI,CAAC,CAACG,QAAQ,CAAC,QAAQ,CAAC;EAC7C;EAEA3B,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,eAAe,CAAC;EAE7C,IAAI;IACF;IACA,MAAMb,gBAAgB,CAAC;MACrBC,UAAU;MACVM,YAAY;MACZ0B,eAAe,EAAE;QACfb,OAAO;QACPS;MACF;IACF,CAAC,CAAC;IAEFxB,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,yBAAyB,CAAC;EACzD,CAAC,CAAC,OAAOqB,GAAG,EAAE;IACZ,MAAMC,MAAM,GAAGxC,eAAe,CAACuC,GAAG,CAAC;IACnC7B,OAAO,CAACW,MAAM,CAACoB,KAAK,CAClBD,MAAM,EACN,oEAAoElC,UAAU,iBAAiBM,YAAY,MAAM4B,MAAM,EACzH,CAAC;IAED,MAAMD,GAAG;EACX;AACF","ignoreList":[]}