@defra/forms-engine-plugin 4.11.3 → 4.13.0

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 (38) hide show
  1. package/.server/server/plugins/engine/beta/form-context.js +5 -4
  2. package/.server/server/plugins/engine/beta/form-context.js.map +1 -1
  3. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  4. package/.server/server/plugins/engine/form-availability.d.ts +16 -0
  5. package/.server/server/plugins/engine/form-availability.js +26 -0
  6. package/.server/server/plugins/engine/form-availability.js.map +1 -0
  7. package/.server/server/plugins/engine/models/FormModel.d.ts +4 -0
  8. package/.server/server/plugins/engine/models/FormModel.js +14 -1
  9. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  10. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +8 -0
  11. package/.server/server/plugins/engine/models/unavailable-view-model.js +21 -0
  12. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -0
  13. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +1 -1
  14. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +5 -5
  15. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  16. package/.server/server/plugins/engine/plugin.js +6 -0
  17. package/.server/server/plugins/engine/plugin.js.map +1 -1
  18. package/.server/server/plugins/engine/routes/questions.js +1 -1
  19. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  20. package/.server/server/plugins/engine/unavailable-response.d.ts +9 -0
  21. package/.server/server/plugins/engine/unavailable-response.js +23 -0
  22. package/.server/server/plugins/engine/unavailable-response.js.map +1 -0
  23. package/.server/server/plugins/engine/views/file-upload.html +2 -1
  24. package/.server/server/plugins/engine/views/unavailable.html +20 -0
  25. package/.server/typings/hapi/index.d.js.map +1 -1
  26. package/package.json +4 -4
  27. package/src/server/plugins/engine/beta/form-context.ts +6 -8
  28. package/src/server/plugins/engine/components/helpers/geospatial.ts +1 -1
  29. package/src/server/plugins/engine/form-availability.ts +31 -0
  30. package/src/server/plugins/engine/models/FormModel.ts +20 -0
  31. package/src/server/plugins/engine/models/unavailable-view-model.ts +36 -0
  32. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +13 -6
  33. package/src/server/plugins/engine/plugin.ts +6 -0
  34. package/src/server/plugins/engine/routes/questions.ts +5 -1
  35. package/src/server/plugins/engine/unavailable-response.ts +29 -0
  36. package/src/server/plugins/engine/views/file-upload.html +2 -1
  37. package/src/server/plugins/engine/views/unavailable.html +20 -0
  38. package/src/typings/hapi/index.d.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","StatusCodes","COMPONENT_STATE_ERROR","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","PaymentField","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","buildMainRecords","buildRepeaterRecords","DEFAULT_PAYMENT_HELP_URL","formatCurrency","formatPaymentDate","FormAction","SummaryPageController","allowSaveAndExit","findPaymentField","model","pages","flatMap","page","collection","fields","find","field","constructor","pageDef","viewName","components","getSummaryViewModel","request","context","viewModel","query","payload","errors","state","paymentField","resolvedAmount","resolveAmount","options","paymentState","getPaymentStateFromState","amount","paymentDetails","buildPaymentDetails","paymentRequired","preAuth","status","paymentPreAuthorized","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","rows","key","text","value","description","reference","createdAt","push","title","summaryList","makeGetRouteHandler","h","paymentComplete","handleFormSubmit","reconcilePaymentState","hasMissingNotificationEmail","view","cacheService","resetComponentStates","name","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","params","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","handleSubmissionError","setConfirmationState","confirmed","formId","referenceNumber","clearState","proceed","getStatusPath","handleInvalidComponentStateError","handlePaymentPreAuthError","handlePaymentSubmissionError","govukError","component","userMessage","yar","flash","getStateKeys","path","shouldResetState","errorType","PaymentExpired","PaymentIncomplete","redirect","getHref","code","SEE_OTHER","redirectPath","undefined","helpUrl","helpLink","helpLinkHtml","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","emailAddress","finaliseComponents","paymentWasCaptured","hasPaymentBeenCaptured","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","badRequest","outputService","submit","err","contact","online","url","capture","metadata","allFields","Set","relevantPages","filter","onSubmit","retrievalKey","sessionId","formSubmissionService","main","repeaters","map","href","flat","paymentItems","getPaymentFieldItems","label","getDisplayStringFromState"],"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 RouteOptions } from '@hapi/hapi'\nimport { StatusCodes } from 'http-status-codes'\n\nimport {\n COMPONENT_STATE_ERROR,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\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 type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n InvalidComponentStateError,\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n buildMainRecords,\n buildRepeaterRecords\n} from '~/src/server/plugins/engine/pageControllers/helpers/submission.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n DEFAULT_PAYMENT_HELP_URL,\n formatCurrency,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n /**\n * Finds the PaymentField component across all pages in the model.\n * Payment pages are skipped in the normal page walk\n */\n private findPaymentField(): PaymentField | undefined {\n return this.model.pages\n .flatMap((page) => page.collection.fields)\n .find((field): field is PaymentField => field instanceof PaymentField)\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, state } = context\n\n const paymentField = this.findPaymentField()\n\n if (paymentField) {\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n state\n )\n const paymentState = paymentField.getPaymentStateFromState(state)\n\n if (paymentState?.amount === resolvedAmount) {\n viewModel.paymentState = paymentState\n viewModel.paymentDetails = this.buildPaymentDetails(\n paymentField,\n paymentState\n )\n }\n\n if (resolvedAmount > 0) {\n viewModel.paymentRequired = true\n }\n if (\n paymentState &&\n paymentState.preAuth?.status === 'success' &&\n paymentState.amount === resolvedAmount\n ) {\n viewModel.paymentPreAuthorized = true\n }\n }\n\n const components = this.collection.getViewModel(payload, errors, query)\n\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n private buildPaymentDetails(\n paymentField: PaymentField,\n paymentState: NonNullable<\n ReturnType<PaymentField['getPaymentStateFromState']>\n >\n ) {\n const rows = [\n {\n key: { text: 'Payment for' },\n value: { text: paymentState.description }\n },\n {\n key: { text: 'Total amount' },\n value: { text: formatCurrency(paymentState.amount) }\n },\n {\n key: { text: 'Reference' },\n value: { text: paymentState.reference }\n }\n ]\n\n if (paymentState.preAuth?.createdAt) {\n rows.push({\n key: { text: 'Date of payment' },\n value: { text: formatPaymentDate(paymentState.preAuth.createdAt) }\n })\n }\n\n return {\n title: { text: 'Payment details' },\n summaryList: { rows }\n }\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: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n // After GOV.UK Pay callback, auto-submit the form instead of\n // showing CYA again. The payment is already pre-authorized.\n if (request.query.paymentComplete === 'true') {\n return this.handleFormSubmit(\n request as unknown as FormRequestPayload,\n context,\n h\n )\n }\n\n await this.reconcilePaymentState(request, context)\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 * Checks if the resolved payment amount has changed since pre-auth\n * and invalidates stale payment state if so.\n */\n private async reconcilePaymentState(\n request: FormRequest,\n context: FormContext\n ) {\n const paymentField = this.findPaymentField()\n\n if (!paymentField) {\n return\n }\n\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n context.state\n )\n const paymentState = paymentField.getPaymentStateFromState(context.state)\n\n if (paymentState && paymentState.amount !== resolvedAmount) {\n const cacheService = getCacheService(request.server)\n await cacheService.resetComponentStates(request, [paymentField.name])\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: FormResponseToolkit\n ) => {\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail\n )\n } catch (error) {\n return this.handleSubmissionError(error, request, h)\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId,\n referenceNumber: context.referenceNumber\n } as FormConfirmationState)\n\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n /**\n * Handles errors during form submission\n */\n private async handleSubmissionError(\n error: unknown,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n if (error instanceof InvalidComponentStateError) {\n return this.handleInvalidComponentStateError(error, request, h)\n }\n\n if (error instanceof PaymentPreAuthError) {\n return this.handlePaymentPreAuthError(error, request, h)\n }\n\n if (error instanceof PaymentSubmissionError) {\n return this.handlePaymentSubmissionError(error, request, h)\n }\n\n throw error\n }\n\n /**\n * Handles InvalidComponentStateError during submission\n */\n private async handleInvalidComponentStateError(\n error: InvalidComponentStateError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n const govukError = createError(error.component.name, error.userMessage)\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n /**\n * Handles PaymentPreAuthError during submission\n */\n private async handlePaymentPreAuthError(\n error: PaymentPreAuthError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n if (error.shouldResetState) {\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n if (error.errorType === PaymentErrorTypes.PaymentExpired) {\n request.yar.flash(PAYMENT_EXPIRED_NOTIFICATION, true, true)\n return this.proceed(request, h, error.component.page?.path)\n }\n }\n\n // Payment page is skipped in the page walk, so proceed() would redirect\n // to an earlier page. For PaymentIncomplete, redirect directly to the\n // payment page using its href.\n if (\n error.errorType === PaymentErrorTypes.PaymentIncomplete &&\n error.component.page\n ) {\n return h\n .redirect(error.component.page.getHref(error.component.page.path))\n .code(StatusCodes.SEE_OTHER)\n }\n\n const govukError = createError(error.component.name, error.userMessage)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n const redirectPath = error.shouldResetState\n ? error.component.page?.path\n : undefined\n\n return this.proceed(request, h, redirectPath)\n }\n\n /**\n * Handles PaymentSubmissionError during submission\n */\n private handlePaymentSubmissionError(\n error: PaymentSubmissionError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const helpUrl = error.helpLink ?? DEFAULT_PAYMENT_HELP_URL\n const helpLinkHtml = ` or you can <a href=\"${helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"govuk-link\">contact us (opens in new tab)</a> and quote your reference number to arrange a refund`\n\n const govukError = createError(\n 'submission',\n `There was a problem and your form was not submitted. Try submitting the form again${helpLinkHtml}.`\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n return this.proceed(request, h)\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\nexport async function submitForm(\n context: FormContext,\n formMetadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string\n) {\n await finaliseComponents(request, formMetadata, context, model)\n\n const paymentWasCaptured = hasPaymentBeenCaptured(context, model)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n try {\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 await model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n } catch (err) {\n if (paymentWasCaptured) {\n throw new PaymentSubmissionError(\n context.referenceNumber,\n formMetadata.contact?.online?.url\n )\n }\n throw err\n }\n}\n\n/**\n * Checks if any payment component has been captured\n */\nfunction hasPaymentBeenCaptured(\n context: FormContext,\n model: FormModel\n): boolean {\n for (const page of model.pages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n const paymentState = field.getPaymentStateFromState(context.state)\n if (paymentState?.capture?.status === 'success') {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext,\n model: FormModel\n) {\n // Get fields from relevant pages (normal components)\n // plus PaymentField from all pages (payment page is skipped in the page walk)\n const allFields = new Set([\n ...context.relevantPages.flatMap((page) => page.collection.fields),\n ...model.pages\n .flatMap((page) => page.collection.fields)\n .filter((field) => field instanceof PaymentField)\n ])\n\n for (const component of allFields) {\n await component.onSubmit(request, metadata, context)\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 main: buildMainRecords(items),\n repeaters: buildRepeaterRecords(items)\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n const items = context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n\n const paymentItems = getPaymentFieldItems(context)\n\n return [...items, ...paymentItems]\n}\n\n/**\n * Gets DetailItems for PaymentField components\n * PaymentField is excluded from summaryDetails for UI but needs to be in submission data\n */\nfunction getPaymentFieldItems(context: FormContext): DetailItemField[] {\n const items: DetailItemField[] = []\n\n for (const page of context.relevantPages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n items.push({\n name: field.name,\n page,\n title: field.title,\n label: field.label,\n field,\n state: context.state,\n href: page.href,\n value: field.getDisplayStringFromState(context.state)\n })\n }\n }\n }\n\n return items\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","StatusCodes","COMPONENT_STATE_ERROR","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","PaymentField","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","buildMainRecords","buildRepeaterRecords","DEFAULT_PAYMENT_HELP_URL","formatCurrency","formatPaymentDate","FormAction","SummaryPageController","allowSaveAndExit","findPaymentField","model","pages","flatMap","page","collection","fields","find","field","constructor","pageDef","viewName","components","getSummaryViewModel","request","context","viewModel","query","payload","errors","state","paymentField","resolvedAmount","resolveAmount","options","paymentState","getPaymentStateFromState","amount","paymentDetails","buildPaymentDetails","paymentRequired","preAuth","status","paymentPreAuthorized","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","rows","key","text","value","description","reference","createdAt","push","title","summaryList","makeGetRouteHandler","h","paymentComplete","handleFormSubmit","reconcilePaymentState","hasMissingNotificationEmail","view","cacheService","resetComponentStates","name","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","params","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","handleSubmissionError","setConfirmationState","confirmed","formId","referenceNumber","clearState","proceed","getStatusPath","handleInvalidComponentStateError","handlePaymentPreAuthError","handlePaymentSubmissionError","govukError","component","userMessage","yar","flash","getStateKeys","path","shouldResetState","errorType","PaymentExpired","PaymentIncomplete","redirect","getHref","code","SEE_OTHER","redirectPath","undefined","helpUrl","helpLink","helpLinkHtml","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","emailAddress","finaliseComponents","paymentWasCaptured","hasPaymentBeenCaptured","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","badRequest","outputService","submit","err","contact","online","url","capture","metadata","allFields","Set","relevantPages","filter","onSubmit","retrievalKey","sessionId","formSubmissionService","main","repeaters","map","href","flat","paymentItems","getPaymentFieldItems","label","getDisplayStringFromState"],"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 RouteOptions } from '@hapi/hapi'\nimport { StatusCodes } from 'http-status-codes'\n\nimport {\n COMPONENT_STATE_ERROR,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\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 type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n InvalidComponentStateError,\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n buildMainRecords,\n buildRepeaterRecords\n} from '~/src/server/plugins/engine/pageControllers/helpers/submission.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n DEFAULT_PAYMENT_HELP_URL,\n formatCurrency,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n /**\n * Finds the PaymentField component across all pages in the model.\n * Payment pages are skipped in the normal page walk\n */\n private findPaymentField(): PaymentField | undefined {\n return this.model.pages\n .flatMap((page) => page.collection.fields)\n .find((field): field is PaymentField => field instanceof PaymentField)\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, state } = context\n\n const paymentField = this.findPaymentField()\n\n if (paymentField) {\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n state\n )\n const paymentState = paymentField.getPaymentStateFromState(state)\n\n if (paymentState?.amount === resolvedAmount) {\n viewModel.paymentState = paymentState\n viewModel.paymentDetails = this.buildPaymentDetails(\n paymentField,\n paymentState\n )\n }\n\n if (resolvedAmount > 0) {\n viewModel.paymentRequired = true\n }\n if (\n paymentState &&\n paymentState.preAuth?.status === 'success' &&\n paymentState.amount === resolvedAmount\n ) {\n viewModel.paymentPreAuthorized = true\n }\n }\n\n const components = this.collection.getViewModel(payload, errors, query)\n\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n private buildPaymentDetails(\n paymentField: PaymentField,\n paymentState: NonNullable<\n ReturnType<PaymentField['getPaymentStateFromState']>\n >\n ) {\n const rows = [\n {\n key: { text: 'Payment for' },\n value: { text: paymentState.description }\n },\n {\n key: { text: 'Total amount' },\n value: { text: formatCurrency(paymentState.amount) }\n },\n {\n key: { text: 'Reference' },\n value: { text: paymentState.reference }\n }\n ]\n\n if (paymentState.preAuth?.createdAt) {\n rows.push({\n key: { text: 'Date of payment' },\n value: { text: formatPaymentDate(paymentState.preAuth.createdAt) }\n })\n }\n\n return {\n title: { text: 'Payment details' },\n summaryList: { rows }\n }\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: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n // After GOV.UK Pay callback, auto-submit the form instead of\n // showing CYA again. The payment is already pre-authorized.\n if (request.query.paymentComplete === 'true') {\n return this.handleFormSubmit(\n request as unknown as FormRequestPayload,\n context,\n h\n )\n }\n\n await this.reconcilePaymentState(request, context)\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 * Checks if the resolved payment amount has changed since pre-auth\n * and invalidates stale payment state if so.\n */\n private async reconcilePaymentState(\n request: FormRequest,\n context: FormContext\n ) {\n const paymentField = this.findPaymentField()\n\n if (!paymentField) {\n return\n }\n\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n context.state\n )\n const paymentState = paymentField.getPaymentStateFromState(context.state)\n\n if (paymentState && paymentState.amount !== resolvedAmount) {\n const cacheService = getCacheService(request.server)\n await cacheService.resetComponentStates(request, [paymentField.name])\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: FormResponseToolkit\n ) => {\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail\n )\n } catch (error) {\n return this.handleSubmissionError(error, request, h)\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId,\n referenceNumber: context.referenceNumber\n } as FormConfirmationState)\n\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n /**\n * Handles errors during form submission\n */\n private async handleSubmissionError(\n error: unknown,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n if (error instanceof InvalidComponentStateError) {\n return this.handleInvalidComponentStateError(error, request, h)\n }\n\n if (error instanceof PaymentPreAuthError) {\n return this.handlePaymentPreAuthError(error, request, h)\n }\n\n if (error instanceof PaymentSubmissionError) {\n return this.handlePaymentSubmissionError(error, request, h)\n }\n\n throw error\n }\n\n /**\n * Handles InvalidComponentStateError during submission\n */\n private async handleInvalidComponentStateError(\n error: InvalidComponentStateError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n const govukError = createError(error.component.name, error.userMessage)\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n /**\n * Handles PaymentPreAuthError during submission\n */\n private async handlePaymentPreAuthError(\n error: PaymentPreAuthError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n if (error.shouldResetState) {\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n if (error.errorType === PaymentErrorTypes.PaymentExpired) {\n request.yar.flash(PAYMENT_EXPIRED_NOTIFICATION, true, true)\n return this.proceed(request, h, error.component.page?.path)\n }\n }\n\n // Payment page is skipped in the page walk, so proceed() would redirect\n // to an earlier page. For PaymentIncomplete, redirect directly to the\n // payment page using its href.\n if (\n error.errorType === PaymentErrorTypes.PaymentIncomplete &&\n error.component.page\n ) {\n return h\n .redirect(error.component.page.getHref(error.component.page.path))\n .code(StatusCodes.SEE_OTHER)\n }\n\n const govukError = createError(error.component.name, error.userMessage)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n const redirectPath = error.shouldResetState\n ? error.component.page?.path\n : undefined\n\n return this.proceed(request, h, redirectPath)\n }\n\n /**\n * Handles PaymentSubmissionError during submission\n */\n private handlePaymentSubmissionError(\n error: PaymentSubmissionError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const helpUrl = error.helpLink ?? DEFAULT_PAYMENT_HELP_URL\n const helpLinkHtml = ` or you can <a href=\"${helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"govuk-link\">contact us (opens in new tab)</a> and quote your reference number to arrange a refund`\n\n const govukError = createError(\n 'submission',\n `There was a problem and your form was not submitted. Try submitting the form again${helpLinkHtml}.`\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n return this.proceed(request, h)\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\nexport async function submitForm(\n context: FormContext,\n formMetadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string\n) {\n await finaliseComponents(request, formMetadata, context, model)\n\n const paymentWasCaptured = hasPaymentBeenCaptured(context, model)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details,\n model\n )\n\n try {\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 await model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n } catch (err) {\n if (paymentWasCaptured) {\n throw new PaymentSubmissionError(\n context.referenceNumber,\n formMetadata.contact?.online?.url\n )\n }\n throw err\n }\n}\n\n/**\n * Checks if any payment component has been captured\n */\nfunction hasPaymentBeenCaptured(\n context: FormContext,\n model: FormModel\n): boolean {\n for (const page of model.pages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n const paymentState = field.getPaymentStateFromState(context.state)\n if (paymentState?.capture?.status === 'success') {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext,\n model: FormModel\n) {\n // Get fields from relevant pages (normal components)\n // plus PaymentField from all pages (payment page is skipped in the page walk)\n const allFields = new Set([\n ...context.relevantPages.flatMap((page) => page.collection.fields),\n ...model.pages\n .flatMap((page) => page.collection.fields)\n .filter((field) => field instanceof PaymentField)\n ])\n\n for (const component of allFields) {\n await component.onSubmit(request, metadata, context)\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 main: buildMainRecords(items),\n repeaters: buildRepeaterRecords(items)\n }\n return submit(payload)\n}\n\nexport function getFormSubmissionData(\n context: FormContext,\n details: Detail[],\n model: FormModel\n) {\n const items = context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n\n const paymentItems = getPaymentFieldItems(context, model)\n\n return [...items, ...paymentItems]\n}\n\n/**\n * Gets DetailItems for PaymentField components\n * PaymentField is excluded from summaryDetails for UI but needs to be in submission data\n */\nfunction getPaymentFieldItems(\n context: FormContext,\n model: FormModel\n): DetailItemField[] {\n const items: DetailItemField[] = []\n\n for (const page of model.pages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n items.push({\n name: field.name,\n page,\n title: field.title,\n label: field.label,\n field,\n state: context.state,\n href: page.href,\n value: field.getDisplayStringFromState(context.state)\n })\n }\n }\n }\n\n return items\n}\n"],"mappings":"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","ignoreList":[]}
@@ -5,6 +5,7 @@ import { getRoutes as getPaymentRoutes } from "./routes/payment.js";
5
5
  import { getRoutes as getQuestionRoutes } from "./routes/questions.js";
6
6
  import { getRoutes as getRepeaterItemDeleteRoutes } from "./routes/repeaters/item-delete.js";
7
7
  import { getRoutes as getRepeaterSummaryRoutes } from "./routes/repeaters/summary.js";
8
+ import { registerUnavailableResponse } from "./unavailable-response.js";
8
9
  import { registerVision } from "./vision.js";
9
10
  import { mapPlugin } from "../map/index.js";
10
11
  import { postcodeLookupPlugin } from "../postcode-lookup/index.js";
@@ -82,6 +83,11 @@ export const plugin = {
82
83
  };
83
84
  const routes = [...getPaymentRoutes(), ...getFileUploadStatusRoutes(), ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions, onRequest), ...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions, onRequest), ...getQuestionRoutes(getRouteOptions, postRouteOptions, preparePageEventRequestOptions, onRequest)];
84
85
  server.route(routes); // TODO
86
+
87
+ // Registration order is important: must be registered after the engine's
88
+ // routes so it sees their responses, but before any global error-page
89
+ // handler that would re-shape Boom errors.
90
+ registerUnavailableResponse(server);
85
91
  }
86
92
  };
87
93
  //# sourceMappingURL=plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getPaymentRoutes","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","mapPlugin","postcodeLookupPlugin","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cache","saveAndExit","nunjucks","nunjucksOptions","viewContext","preparePageEventRequestOptions","onRequest","ordnanceSurveyApiKey","baseUrl","ordnanceSurveyApiSecret","services","cacheService","cacheName","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getPaymentRoutes } from '~/src/server/plugins/engine/routes/payment.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport { mapPlugin } from '~/src/server/plugins/map/index.js'\nimport { postcodeLookupPlugin } from '~/src/server/plugins/postcode-lookup/index.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cache,\n saveAndExit,\n nunjucks: nunjucksOptions,\n viewContext,\n preparePageEventRequestOptions,\n onRequest,\n ordnanceSurveyApiKey,\n baseUrl,\n ordnanceSurveyApiSecret,\n services\n } = options\n\n const cacheService =\n typeof cache === 'string'\n ? new CacheService({ server, cacheName: cache })\n : cache\n\n await registerVision(server, options)\n\n // Register the postcode lookup plugin only if we have an OS api key\n if (ordnanceSurveyApiKey) {\n await server.register({\n plugin: postcodeLookupPlugin,\n options: {\n ordnanceSurveyApiKey\n }\n })\n }\n\n // Register the maps plugin only if we have an OS api key & secret\n if (ordnanceSurveyApiKey && ordnanceSurveyApiSecret) {\n await server.register({\n plugin: mapPlugin,\n options: {\n ordnanceSurveyApiKey,\n ordnanceSurveyApiSecret\n }\n })\n }\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n server.expose('saveAndExit', saveAndExit)\n server.expose('baseUrl', baseUrl)\n server.expose('services', services)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...getPaymentRoutes(),\n ...getFileUploadStatusRoutes(),\n ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions, onRequest),\n ...getRepeaterItemDeleteRoutes(\n getRouteOptions,\n postRouteOptions,\n onRequest\n ),\n\n ...getQuestionRoutes(\n getRouteOptions,\n postRouteOptions,\n preparePageEventRequestOptions,\n onRequest\n )\n ]\n\n server.route(routes as unknown as ServerRoute[]) // TODO\n }\n} satisfies Plugin<PluginOptions>\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getPaymentRoutes","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerUnavailableResponse","registerVision","mapPlugin","postcodeLookupPlugin","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cache","saveAndExit","nunjucks","nunjucksOptions","viewContext","preparePageEventRequestOptions","onRequest","ordnanceSurveyApiKey","baseUrl","ordnanceSurveyApiSecret","services","cacheService","cacheName","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getPaymentRoutes } from '~/src/server/plugins/engine/routes/payment.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerUnavailableResponse } from '~/src/server/plugins/engine/unavailable-response.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport { mapPlugin } from '~/src/server/plugins/map/index.js'\nimport { postcodeLookupPlugin } from '~/src/server/plugins/postcode-lookup/index.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cache,\n saveAndExit,\n nunjucks: nunjucksOptions,\n viewContext,\n preparePageEventRequestOptions,\n onRequest,\n ordnanceSurveyApiKey,\n baseUrl,\n ordnanceSurveyApiSecret,\n services\n } = options\n\n const cacheService =\n typeof cache === 'string'\n ? new CacheService({ server, cacheName: cache })\n : cache\n\n await registerVision(server, options)\n\n // Register the postcode lookup plugin only if we have an OS api key\n if (ordnanceSurveyApiKey) {\n await server.register({\n plugin: postcodeLookupPlugin,\n options: {\n ordnanceSurveyApiKey\n }\n })\n }\n\n // Register the maps plugin only if we have an OS api key & secret\n if (ordnanceSurveyApiKey && ordnanceSurveyApiSecret) {\n await server.register({\n plugin: mapPlugin,\n options: {\n ordnanceSurveyApiKey,\n ordnanceSurveyApiSecret\n }\n })\n }\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n server.expose('saveAndExit', saveAndExit)\n server.expose('baseUrl', baseUrl)\n server.expose('services', services)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...getPaymentRoutes(),\n ...getFileUploadStatusRoutes(),\n ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions, onRequest),\n ...getRepeaterItemDeleteRoutes(\n getRouteOptions,\n postRouteOptions,\n onRequest\n ),\n\n ...getQuestionRoutes(\n getRouteOptions,\n postRouteOptions,\n preparePageEventRequestOptions,\n onRequest\n )\n ]\n\n server.route(routes as unknown as ServerRoute[]) // TODO\n\n // Registration order is important: must be registered after the engine's\n // routes so it sees their responses, but before any global error-page\n // handler that would re-shape Boom errors.\n registerUnavailableResponse(server)\n }\n} satisfies Plugin<PluginOptions>\n"],"mappings":"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","ignoreList":[]}
@@ -19,7 +19,7 @@ async function handleHttpEvent(request, page, context, event, model, preparePage
19
19
  // TODO: Update structured data POST payload with when helper
20
20
  // is updated to removing the dependency on `SummaryViewModel` etc.
21
21
  const viewModel = new SummaryViewModel(request, page, context);
22
- const items = getFormSubmissionData(viewModel.context, viewModel.details);
22
+ const items = getFormSubmissionData(viewModel.context, viewModel.details, model);
23
23
 
24
24
  // @ts-expect-error - function signature will be refactored in the next iteration of the formatter
25
25
  const payload = format(context, items, model, undefined, undefined);
@@ -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","timeout","response","postJson","Object","assign","data","makeGetHandler","onRequest","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 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 AnyFormRequest,\n type FormContext,\n type OnRequestCallback,\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 type FormResponseToolkit\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: AnyFormRequest,\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: httpService.RequestOptions = { payload, timeout: 5000 }\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 onRequest?: OnRequestCallback\n) {\n return function getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n 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?.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}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n 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 (events?.onSave?.type === 'http' && isSuccessful(response)) {\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}\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 onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\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, onRequest),\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, onRequest),\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, onRequest),\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, onRequest),\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","timeout","response","postJson","Object","assign","data","makeGetHandler","onRequest","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 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 AnyFormRequest,\n type FormContext,\n type OnRequestCallback,\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 type FormResponseToolkit\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: AnyFormRequest,\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(\n viewModel.context,\n viewModel.details,\n model\n )\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: httpService.RequestOptions = { payload, timeout: 5000 }\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 onRequest?: OnRequestCallback\n) {\n return function getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n 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?.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}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n 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 (events?.onSave?.type === 'http' && isSuccessful(response)) {\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}\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 onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\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, onRequest),\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, onRequest),\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, onRequest),\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, onRequest),\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":[]}
@@ -0,0 +1,9 @@
1
+ import { type Server } from '@hapi/hapi';
2
+ /**
3
+ * Registers a server-wide onPreResponse extension that intercepts the offline
4
+ * Boom thrown and renders the unavailable view.
5
+ *
6
+ * Must be registered after the engine's routes so it sees their responses,
7
+ * but before any global error-page handler that would re-shape Boom errors.
8
+ */
9
+ export declare function registerUnavailableResponse(server: Server): void;
@@ -0,0 +1,23 @@
1
+ import { isOfflineBoom } from "./form-availability.js";
2
+ import { unavailableViewModel } from "./models/unavailable-view-model.js";
3
+
4
+ /**
5
+ * Registers a server-wide onPreResponse extension that intercepts the offline
6
+ * Boom thrown and renders the unavailable view.
7
+ *
8
+ * Must be registered after the engine's routes so it sees their responses,
9
+ * but before any global error-page handler that would re-shape Boom errors.
10
+ */
11
+ export function registerUnavailableResponse(server) {
12
+ server.ext('onPreResponse', (request, h) => {
13
+ const response = request.response;
14
+ if (!isOfflineBoom(response)) {
15
+ return h.continue;
16
+ }
17
+ const {
18
+ metadata
19
+ } = response.data;
20
+ return h.view('unavailable', unavailableViewModel(metadata)).header('Cache-Control', 'no-store, no-cache, must-revalidate').header('X-Robots-Tag', 'noindex, nofollow').code(200).takeover();
21
+ });
22
+ }
23
+ //# sourceMappingURL=unavailable-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unavailable-response.js","names":["isOfflineBoom","unavailableViewModel","registerUnavailableResponse","server","ext","request","h","response","continue","metadata","data","view","header","code","takeover"],"sources":["../../../../src/server/plugins/engine/unavailable-response.ts"],"sourcesContent":["import { type Request, type ResponseToolkit, type Server } from '@hapi/hapi'\n\nimport { isOfflineBoom } from '~/src/server/plugins/engine/form-availability.js'\nimport { unavailableViewModel } from '~/src/server/plugins/engine/models/unavailable-view-model.js'\n\n/**\n * Registers a server-wide onPreResponse extension that intercepts the offline\n * Boom thrown and renders the unavailable view.\n *\n * Must be registered after the engine's routes so it sees their responses,\n * but before any global error-page handler that would re-shape Boom errors.\n */\nexport function registerUnavailableResponse(server: Server) {\n server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {\n const response = request.response\n if (!isOfflineBoom(response)) {\n return h.continue\n }\n\n const { metadata } = response.data\n\n return h\n .view('unavailable', unavailableViewModel(metadata))\n .header('Cache-Control', 'no-store, no-cache, must-revalidate')\n .header('X-Robots-Tag', 'noindex, nofollow')\n .code(200)\n .takeover()\n })\n}\n"],"mappings":"AAEA,SAASA,aAAa;AACtB,SAASC,oBAAoB;;AAE7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,2BAA2BA,CAACC,MAAc,EAAE;EAC1DA,MAAM,CAACC,GAAG,CAAC,eAAe,EAAE,CAACC,OAAgB,EAAEC,CAAkB,KAAK;IACpE,MAAMC,QAAQ,GAAGF,OAAO,CAACE,QAAQ;IACjC,IAAI,CAACP,aAAa,CAACO,QAAQ,CAAC,EAAE;MAC5B,OAAOD,CAAC,CAACE,QAAQ;IACnB;IAEA,MAAM;MAAEC;IAAS,CAAC,GAAGF,QAAQ,CAACG,IAAI;IAElC,OAAOJ,CAAC,CACLK,IAAI,CAAC,aAAa,EAAEV,oBAAoB,CAACQ,QAAQ,CAAC,CAAC,CACnDG,MAAM,CAAC,eAAe,EAAE,qCAAqC,CAAC,CAC9DA,MAAM,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAC3CC,IAAI,CAAC,GAAG,CAAC,CACTC,QAAQ,CAAC,CAAC;EACf,CAAC,CAAC;AACJ","ignoreList":[]}
@@ -16,7 +16,8 @@
16
16
  {{ govukButton({
17
17
  text: "Upload file",
18
18
  classes: "govuk-button--secondary upload-file-button",
19
- preventDoubleClick: true
19
+ preventDoubleClick: true,
20
+ disabled: context.isForceAccess
20
21
  }) }}
21
22
  {% else %}
22
23
  {{ govukWarningText({
@@ -0,0 +1,20 @@
1
+ {% extends baseLayoutPath %}
2
+
3
+ {% block content %}
4
+ <div class="govuk-grid-row">
5
+ <div class="govuk-grid-column-two-thirds">
6
+ <h1 class="govuk-heading-l">Sorry, this form is unavailable</h1>
7
+ <p class="govuk-body">'{{ formTitle }}' has been archived and is no longer available.</p>
8
+ <p class="govuk-body">Contact the {{ organisationName }}.</p>
9
+
10
+ {% if phoneLines %}
11
+ <ul class="govuk-list govuk-list--bullet">
12
+ {% for line in phoneLines %}
13
+ <li>{{ line }}</li>
14
+ {% endfor %}
15
+ </ul>
16
+ <p class="govuk-body"><a href="https://www.gov.uk/call-charges" class="govuk-link govuk-link--no-visited-state">Find out about call charges</a></p>
17
+ {% endif %}
18
+ </div>
19
+ </div>
20
+ {% endblock %}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["import { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type COMPONENT_STATE_ERROR,\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [COMPONENT_STATE_ERROR]: string\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n baseUrl: string\n services: PluginOptions['services']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["import { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type COMPONENT_STATE_ERROR,\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [COMPONENT_STATE_ERROR]: string\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n baseUrl: string\n services: PluginOptions['services']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models?: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.11.3",
3
+ "version": "4.13.0",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -44,10 +44,10 @@
44
44
  "dev:debug": "concurrently \"npm run client:watch\" \"npm run server:watch:debug\" --kill-others --names \"client,server\" --prefix-colors \"red.dim,blue.dim\"",
45
45
  "format": "npm run format:check -- --write",
46
46
  "format:check": "prettier --cache --cache-location .cache/prettier --cache-strategy content --check \"**/*.{cjs,js,json,md,mjs,scss,ts}\"",
47
- "predocs:dev": "node scripts/generate-schema-docs.js && node scripts/generate-component-docs.js",
47
+ "predocs:dev": "node scripts/generate-schema-docs.js && tsx scripts/generate-component-docs.js",
48
48
  "docs:dev": "BROWSERSLIST_ENV=javascripts docusaurus start --host 0.0.0.0",
49
49
  "docs:build": "BROWSERSLIST_ENV=javascripts docusaurus build",
50
- "docs:build:all": "node scripts/generate-schema-docs.js && node scripts/generate-component-docs.js && npm run docs:build",
50
+ "docs:build:all": "node scripts/generate-schema-docs.js && tsx scripts/generate-component-docs.js && npm run docs:build",
51
51
  "docs:serve": "docusaurus serve --host 0.0.0.0",
52
52
  "docs:clear": "docusaurus clear",
53
53
  "generate-schema-docs": "node scripts/generate-schema-docs.js",
@@ -87,7 +87,7 @@
87
87
  },
88
88
  "license": "SEE LICENSE IN LICENSE",
89
89
  "dependencies": {
90
- "@defra/forms-model": "^3.0.655",
90
+ "@defra/forms-model": "^3.0.663",
91
91
  "@defra/hapi-tracing": "^1.29.0",
92
92
  "@defra/interactive-map": "^0.0.22-alpha",
93
93
  "@elastic/ecs-pino-format": "^1.5.0",
@@ -3,6 +3,7 @@ import { type Request, type Server } from '@hapi/hapi'
3
3
  import { isEqual } from 'date-fns'
4
4
 
5
5
  import { PREVIEW_PATH_PREFIX } from '../../../constants.js'
6
+ import { assertFormAvailable } from '../form-availability.js'
6
7
  import {
7
8
  checkEmailAddressForLiveFormSubmission,
8
9
  getCacheService
@@ -52,6 +53,7 @@ export async function getFormModel(
52
53
  const formState = resolveState(state)
53
54
 
54
55
  const metadata = await formsService.getFormMetadata(slug)
56
+ assertFormAvailable(metadata)
55
57
 
56
58
  const definition = await formsService.getFormDefinition(
57
59
  metadata.id,
@@ -134,6 +136,7 @@ export async function resolveFormModel(
134
136
  const { formsService } = services
135
137
 
136
138
  const metadata = await formsService.getFormMetadata(slug)
139
+ assertFormAvailable(metadata)
137
140
  const formState = resolveState(state)
138
141
  const isPreview = options.isPreview ?? isPreviewState(state, options)
139
142
  const stateMetadata = metadata[formState]
@@ -145,15 +148,10 @@ export async function resolveFormModel(
145
148
  }
146
149
 
147
150
  // The models cache is created lazily per server instance
148
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
149
- if (!server.app.models) {
150
- server.app.models = new Map<string, { model: FormModel; updatedAt: Date }>()
151
- }
152
151
 
153
- const cache = server.app.models as Map<
154
- string,
155
- { model: FormModel; updatedAt: Date }
156
- >
152
+ server.app.models ??= new Map<string, { model: FormModel; updatedAt: Date }>()
153
+
154
+ const cache = server.app.models
157
155
 
158
156
  const cacheKey = `${metadata.id}_${formState}_${isPreview}`
159
157
  let entry = cache.get(cacheKey)
@@ -116,7 +116,7 @@ export function getGeospatialSchema(country?: GeospatialFieldOptionsCountry) {
116
116
  return value
117
117
  }
118
118
 
119
- const result = booleanWithin(value, countryFeature)
119
+ const result = booleanWithin(value as Geometry | Feature, countryFeature)
120
120
 
121
121
  if (!result) {
122
122
  return helpers.error('any.custom', {
@@ -0,0 +1,31 @@
1
+ import { type FormMetadata } from '@defra/forms-model'
2
+ import Boom from '@hapi/boom'
3
+
4
+ export interface OfflineBoomData {
5
+ offline: true
6
+ metadata: FormMetadata
7
+ }
8
+
9
+ /**
10
+ * Throws when the form has been taken offline. The plugin's
11
+ * unavailable-response extension catches the marker and renders the
12
+ * "Sorry, this form is unavailable" view at HTTP 200.
13
+ */
14
+ export function assertFormAvailable(metadata: FormMetadata): void {
15
+ if (metadata.offline === true) {
16
+ const data: OfflineBoomData = { offline: true, metadata }
17
+ throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
18
+ statusCode: 503,
19
+ data
20
+ })
21
+ }
22
+ }
23
+
24
+ /** Type guard for the offline Boom marker. */
25
+ export function isOfflineBoom(
26
+ err: unknown
27
+ ): err is Boom.Boom<OfflineBoomData> & { data: OfflineBoomData } {
28
+ if (!Boom.isBoom(err)) return false
29
+ const data = err.data as Partial<OfflineBoomData> | null | undefined
30
+ return data?.offline === true && !!data.metadata
31
+ }
@@ -9,6 +9,7 @@ import {
9
9
  formDefinitionV2Schema,
10
10
  generateConditionAlias,
11
11
  hasComponents,
12
+ hasComponentsEvenIfNoNext,
12
13
  hasRepeater,
13
14
  isConditionWrapperV2,
14
15
  yesNoListId,
@@ -159,6 +160,13 @@ export class FormModel {
159
160
  this.services = services
160
161
  this.controllers = controllers
161
162
 
163
+ // Assert that there is only one payment question (if any)
164
+ if (this.moreThanOnePaymentQuestion()) {
165
+ throw new Error(
166
+ 'Invalid form definition: Only one payment question is allowed per form'
167
+ )
168
+ }
169
+
162
170
  this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))
163
171
  this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))
164
172
  this.listDefIdMap = new Map(
@@ -543,6 +551,18 @@ export class FormModel {
543
551
  .filter(isConditionWrapperV2)
544
552
  .find((condition) => condition.id === conditionId)
545
553
  }
554
+
555
+ /**
556
+ * Checks that only one payment field exists (if any payments fields exist)
557
+ */
558
+ moreThanOnePaymentQuestion() {
559
+ const numOfPaymentFields = this.def.pages
560
+ .flatMap((page) =>
561
+ hasComponentsEvenIfNoNext(page) ? page.components : []
562
+ )
563
+ .filter((comp) => comp.type === ComponentType.PaymentField).length
564
+ return numOfPaymentFields > 1
565
+ }
546
566
  }
547
567
 
548
568
  /**
@@ -0,0 +1,36 @@
1
+ import { type FormMetadata } from '@defra/forms-model'
2
+
3
+ export interface UnavailableViewModel {
4
+ pageTitle: string
5
+ formTitle: string
6
+ organisationName: string
7
+ phoneLines?: string[]
8
+ }
9
+
10
+ /**
11
+ * Defra organisations carry an abbreviation suffix on the enum value, e.g.
12
+ * "Rural Payments Agency – RPA". The unavailable page reads cleanly without it.
13
+ */
14
+ function stripOrgSuffix(organisation: string) {
15
+ return organisation.split(' – ')[0]
16
+ }
17
+
18
+ function splitPhoneLines(phone: string | undefined) {
19
+ if (!phone) return undefined
20
+ const lines = phone
21
+ .split('\n')
22
+ .map((line) => line.trim())
23
+ .filter((line) => line.length > 0)
24
+ return lines.length > 0 ? lines : undefined
25
+ }
26
+
27
+ export function unavailableViewModel(
28
+ metadata: FormMetadata
29
+ ): UnavailableViewModel {
30
+ return {
31
+ pageTitle: 'Sorry, this form is unavailable',
32
+ formTitle: metadata.title,
33
+ organisationName: stripOrgSuffix(metadata.organisation),
34
+ phoneLines: splitPhoneLines(metadata.contact?.phone)
35
+ }
36
+ }