@defra/forms-engine-plugin 0.0.4 → 0.0.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 (260) hide show
  1. package/.server/server/index.js +0 -4
  2. package/.server/server/index.js.map +1 -1
  3. package/.server/server/plugins/engine/helpers.js +3 -0
  4. package/.server/server/plugins/engine/helpers.js.map +1 -1
  5. package/.server/server/plugins/engine/index.js +27 -1
  6. package/.server/server/plugins/engine/index.js.map +1 -1
  7. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +2 -4
  8. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  9. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +4 -10
  10. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  11. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +2 -3
  12. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  13. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +2 -4
  14. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  15. package/.server/server/plugins/engine/plugin.js +65 -6
  16. package/.server/server/plugins/engine/plugin.js.map +1 -1
  17. package/.server/server/plugins/engine/types.js.map +1 -1
  18. package/.server/server/{views → plugins/engine/views}/components/service-banner/template.test.js +1 -1
  19. package/.server/server/plugins/engine/views/components/service-banner/template.test.js.map +1 -0
  20. package/.server/server/{views → plugins/engine/views}/components/tag-env/template.test.js +1 -1
  21. package/.server/server/plugins/engine/views/components/tag-env/template.test.js.map +1 -0
  22. package/.server/server/services/cacheService.js +5 -2
  23. package/.server/server/services/cacheService.js.map +1 -1
  24. package/.server/typings/hapi/index.d.js.map +1 -1
  25. package/README.md +215 -4
  26. package/package.json +3 -3
  27. package/src/client/javascripts/application.js +87 -0
  28. package/src/client/javascripts/file-upload.js +386 -0
  29. package/src/client/stylesheets/_code.scss +33 -0
  30. package/src/client/stylesheets/_govuk-frontend.scss +4 -0
  31. package/src/client/stylesheets/_prose.scss +56 -0
  32. package/src/client/stylesheets/_service-banner.scss +24 -0
  33. package/src/client/stylesheets/_summary-list.scss +28 -0
  34. package/src/client/stylesheets/_tag-env.scss +24 -0
  35. package/src/client/stylesheets/application.scss +14 -0
  36. package/src/common/cookies.js +58 -0
  37. package/src/common/cookies.test.js +23 -0
  38. package/src/common/types.js +5 -0
  39. package/src/config/index.ts +271 -0
  40. package/src/index.ts +31 -0
  41. package/src/server/common/helpers/logging/logger-options.test.ts +50 -0
  42. package/src/server/common/helpers/logging/logger-options.ts +46 -0
  43. package/src/server/common/helpers/logging/logger.ts +7 -0
  44. package/src/server/common/helpers/logging/request-logger.ts +9 -0
  45. package/src/server/common/helpers/logging/request-tracing.js +10 -0
  46. package/src/server/common/helpers/redis-client.js +70 -0
  47. package/src/server/constants.js +1 -0
  48. package/src/server/forms/README.md +10 -0
  49. package/src/server/forms/components.json +1015 -0
  50. package/src/server/forms/report-a-terrorist.json +270 -0
  51. package/src/server/forms/runner-components-test.json +365 -0
  52. package/src/server/forms/test.json +581 -0
  53. package/src/server/index.test.ts +582 -0
  54. package/src/server/index.ts +135 -0
  55. package/src/server/plugins/blankie.test.ts +73 -0
  56. package/src/server/plugins/blankie.ts +48 -0
  57. package/src/server/plugins/crumb.ts +20 -0
  58. package/src/server/plugins/engine/README.md +87 -0
  59. package/src/server/plugins/engine/components/AutocompleteField.test.ts +294 -0
  60. package/src/server/plugins/engine/components/AutocompleteField.ts +49 -0
  61. package/src/server/plugins/engine/components/CheckboxesField.test.ts +379 -0
  62. package/src/server/plugins/engine/components/CheckboxesField.ts +106 -0
  63. package/src/server/plugins/engine/components/ComponentBase.ts +97 -0
  64. package/src/server/plugins/engine/components/ComponentCollection.ts +278 -0
  65. package/src/server/plugins/engine/components/DatePartsField.test.ts +822 -0
  66. package/src/server/plugins/engine/components/DatePartsField.ts +264 -0
  67. package/src/server/plugins/engine/components/Details.test.ts +49 -0
  68. package/src/server/plugins/engine/components/Details.ts +30 -0
  69. package/src/server/plugins/engine/components/EmailAddressField.test.ts +395 -0
  70. package/src/server/plugins/engine/components/EmailAddressField.ts +55 -0
  71. package/src/server/plugins/engine/components/FileUploadField.test.ts +778 -0
  72. package/src/server/plugins/engine/components/FileUploadField.ts +262 -0
  73. package/src/server/plugins/engine/components/FormComponent.ts +249 -0
  74. package/src/server/plugins/engine/components/Html.test.ts +48 -0
  75. package/src/server/plugins/engine/components/Html.ts +29 -0
  76. package/src/server/plugins/engine/components/InsetText.test.ts +48 -0
  77. package/src/server/plugins/engine/components/InsetText.ts +27 -0
  78. package/src/server/plugins/engine/components/List.test.ts +76 -0
  79. package/src/server/plugins/engine/components/List.ts +72 -0
  80. package/src/server/plugins/engine/components/ListFormComponent.ts +140 -0
  81. package/src/server/plugins/engine/components/MonthYearField.test.ts +567 -0
  82. package/src/server/plugins/engine/components/MonthYearField.ts +222 -0
  83. package/src/server/plugins/engine/components/MultilineTextField.test.ts +558 -0
  84. package/src/server/plugins/engine/components/MultilineTextField.ts +138 -0
  85. package/src/server/plugins/engine/components/NumberField.test.ts +701 -0
  86. package/src/server/plugins/engine/components/NumberField.ts +163 -0
  87. package/src/server/plugins/engine/components/RadiosField.test.ts +288 -0
  88. package/src/server/plugins/engine/components/RadiosField.ts +24 -0
  89. package/src/server/plugins/engine/components/SelectField.test.ts +288 -0
  90. package/src/server/plugins/engine/components/SelectField.ts +47 -0
  91. package/src/server/plugins/engine/components/SelectionControlField.ts +43 -0
  92. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +356 -0
  93. package/src/server/plugins/engine/components/TelephoneNumberField.ts +67 -0
  94. package/src/server/plugins/engine/components/TextField.test.ts +489 -0
  95. package/src/server/plugins/engine/components/TextField.ts +96 -0
  96. package/src/server/plugins/engine/components/UkAddressField.test.ts +623 -0
  97. package/src/server/plugins/engine/components/UkAddressField.ts +172 -0
  98. package/src/server/plugins/engine/components/YesNoField.test.ts +248 -0
  99. package/src/server/plugins/engine/components/YesNoField.ts +31 -0
  100. package/src/server/plugins/engine/components/constants.ts +1 -0
  101. package/src/server/plugins/engine/components/helpers.ts +330 -0
  102. package/src/server/plugins/engine/components/index.ts +24 -0
  103. package/src/server/plugins/engine/components/types.ts +117 -0
  104. package/src/server/plugins/engine/configureEnginePlugin.ts +47 -0
  105. package/src/server/plugins/engine/helpers.test.ts +791 -0
  106. package/src/server/plugins/engine/helpers.ts +384 -0
  107. package/src/server/plugins/engine/index.ts +47 -0
  108. package/src/server/plugins/engine/models/FormModel.test.ts +42 -0
  109. package/src/server/plugins/engine/models/FormModel.ts +443 -0
  110. package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
  111. package/src/server/plugins/engine/models/Section.ts +0 -0
  112. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +209 -0
  113. package/src/server/plugins/engine/models/SummaryViewModel.ts +220 -0
  114. package/src/server/plugins/engine/models/index.ts +2 -0
  115. package/src/server/plugins/engine/models/types.ts +114 -0
  116. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +143 -0
  117. package/src/server/plugins/engine/outputFormatters/human/v1.ts +73 -0
  118. package/src/server/plugins/engine/outputFormatters/index.test.ts +17 -0
  119. package/src/server/plugins/engine/outputFormatters/index.ts +44 -0
  120. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +229 -0
  121. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +140 -0
  122. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +229 -0
  123. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +153 -0
  124. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1116 -0
  125. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +447 -0
  126. package/src/server/plugins/engine/pageControllers/PageController.test.ts +205 -0
  127. package/src/server/plugins/engine/pageControllers/PageController.ts +176 -0
  128. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1264 -0
  129. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +565 -0
  130. package/src/server/plugins/engine/pageControllers/README.md +28 -0
  131. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +264 -0
  132. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +458 -0
  133. package/src/server/plugins/engine/pageControllers/StartPageController.ts +18 -0
  134. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +51 -0
  135. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +262 -0
  136. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +28 -0
  137. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +19 -0
  138. package/src/server/plugins/engine/pageControllers/helpers.test.ts +198 -0
  139. package/src/server/plugins/engine/pageControllers/helpers.ts +101 -0
  140. package/src/server/plugins/engine/pageControllers/index.ts +10 -0
  141. package/src/server/plugins/engine/pageControllers/validationOptions.ts +89 -0
  142. package/src/server/plugins/engine/plugin.ts +753 -0
  143. package/src/server/plugins/engine/services/formSubmissionService.js +46 -0
  144. package/src/server/plugins/engine/services/formsService.js +46 -0
  145. package/src/server/plugins/engine/services/formsService.test.js +90 -0
  146. package/src/server/plugins/engine/services/index.js +3 -0
  147. package/src/server/plugins/engine/services/notifyService.test.ts +132 -0
  148. package/src/server/plugins/engine/services/notifyService.ts +64 -0
  149. package/src/server/plugins/engine/services/uploadService.js +60 -0
  150. package/src/server/plugins/engine/types.ts +317 -0
  151. package/src/server/plugins/engine/views/components/autocompletefield.html +5 -0
  152. package/src/server/plugins/engine/views/components/checkboxesfield.html +5 -0
  153. package/src/server/plugins/engine/views/components/datepartsfield.html +5 -0
  154. package/src/server/plugins/engine/views/components/debug/macro.njk +3 -0
  155. package/src/server/plugins/engine/views/components/debug/template.njk +13 -0
  156. package/src/server/plugins/engine/views/components/details.html +6 -0
  157. package/src/server/plugins/engine/views/components/emailaddressfield.html +5 -0
  158. package/src/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
  159. package/src/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
  160. package/src/server/plugins/engine/views/components/fileuploadfield.html +24 -0
  161. package/src/server/plugins/engine/views/components/html.html +3 -0
  162. package/src/server/plugins/engine/views/components/insettext.html +7 -0
  163. package/src/server/plugins/engine/views/components/list.html +36 -0
  164. package/src/server/plugins/engine/views/components/monthyearfield.html +5 -0
  165. package/src/server/plugins/engine/views/components/multilinetextfield.html +10 -0
  166. package/src/server/plugins/engine/views/components/numberfield.html +5 -0
  167. package/src/server/plugins/engine/views/components/radiosfield.html +5 -0
  168. package/src/server/plugins/engine/views/components/selectfield.html +5 -0
  169. package/src/server/plugins/engine/views/components/service-banner/macro.njk +3 -0
  170. package/src/server/plugins/engine/views/components/service-banner/template.njk +20 -0
  171. package/src/server/plugins/engine/views/components/service-banner/template.test.js +43 -0
  172. package/src/server/plugins/engine/views/components/tag-env/macro.njk +3 -0
  173. package/src/server/plugins/engine/views/components/tag-env/template.njk +30 -0
  174. package/src/server/plugins/engine/views/components/tag-env/template.test.js +66 -0
  175. package/src/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
  176. package/src/server/plugins/engine/views/components/textfield.html +5 -0
  177. package/src/server/plugins/engine/views/components/ukaddressfield.html +25 -0
  178. package/src/server/plugins/engine/views/components/yesnofield.html +5 -0
  179. package/src/server/plugins/engine/views/confirmation.html +19 -0
  180. package/src/server/plugins/engine/views/file-upload.html +45 -0
  181. package/src/server/plugins/engine/views/index.html +39 -0
  182. package/src/server/plugins/engine/views/item-delete.html +56 -0
  183. package/src/server/plugins/engine/views/layout.html +199 -0
  184. package/src/server/plugins/engine/views/partials/components.html +6 -0
  185. package/src/server/plugins/engine/views/partials/conditional-components.html +3 -0
  186. package/src/server/plugins/engine/views/partials/debug.html +44 -0
  187. package/src/server/plugins/engine/views/partials/form.html +15 -0
  188. package/src/server/plugins/engine/views/partials/heading.html +16 -0
  189. package/src/server/plugins/engine/views/partials/preview-banner.html +32 -0
  190. package/src/server/plugins/engine/views/partials/preview-banner.test.js +122 -0
  191. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
  192. package/src/server/plugins/engine/views/repeat-list-summary.html +53 -0
  193. package/src/server/plugins/engine/views/summary.html +50 -0
  194. package/src/server/plugins/errorPages.ts +58 -0
  195. package/src/server/plugins/nunjucks/context.js +88 -0
  196. package/src/server/plugins/nunjucks/context.test.js +142 -0
  197. package/src/server/plugins/nunjucks/enviroment.test.js +201 -0
  198. package/src/server/plugins/nunjucks/environment.js +116 -0
  199. package/src/server/plugins/nunjucks/filters/answer.js +27 -0
  200. package/src/server/plugins/nunjucks/filters/answer.test.js +89 -0
  201. package/src/server/plugins/nunjucks/filters/evaluate.js +21 -0
  202. package/src/server/plugins/nunjucks/filters/field.js +28 -0
  203. package/src/server/plugins/nunjucks/filters/field.test.js +75 -0
  204. package/src/server/plugins/nunjucks/filters/highlight.js +11 -0
  205. package/src/server/plugins/nunjucks/filters/href.js +30 -0
  206. package/src/server/plugins/nunjucks/filters/href.test.js +80 -0
  207. package/src/server/plugins/nunjucks/filters/index.js +8 -0
  208. package/src/server/plugins/nunjucks/filters/inspect.js +15 -0
  209. package/src/server/plugins/nunjucks/filters/page.js +24 -0
  210. package/src/server/plugins/nunjucks/filters/page.test.js +65 -0
  211. package/src/server/plugins/nunjucks/index.js +3 -0
  212. package/src/server/plugins/nunjucks/plugin.js +40 -0
  213. package/src/server/plugins/nunjucks/render.js +42 -0
  214. package/src/server/plugins/nunjucks/types.js +40 -0
  215. package/src/server/plugins/pulse.ts +11 -0
  216. package/src/server/plugins/router.ts +201 -0
  217. package/src/server/plugins/session.ts +28 -0
  218. package/src/server/routes/health.js +13 -0
  219. package/src/server/routes/health.test.js +35 -0
  220. package/src/server/routes/index.test.ts +125 -0
  221. package/src/server/routes/index.ts +2 -0
  222. package/src/server/routes/public.ts +47 -0
  223. package/src/server/routes/types.ts +48 -0
  224. package/src/server/schemas/index.ts +34 -0
  225. package/src/server/secure-context.js +43 -0
  226. package/src/server/services/cacheService.test.ts +277 -0
  227. package/src/server/services/cacheService.ts +138 -0
  228. package/src/server/services/httpService.test.js +491 -0
  229. package/src/server/services/httpService.ts +50 -0
  230. package/src/server/services/index.ts +1 -0
  231. package/src/server/types.ts +54 -0
  232. package/src/server/utils/notify.test.ts +37 -0
  233. package/src/server/utils/notify.ts +50 -0
  234. package/src/server/utils/secure-context/get-trust-store-certs.js +11 -0
  235. package/src/server/utils/secure-context/get-trust-store-certs.test.js +19 -0
  236. package/src/server/utils/utils.js +24 -0
  237. package/src/server/utils/utils.test.js +54 -0
  238. package/src/server/views/404.html +16 -0
  239. package/src/server/views/500.html +19 -0
  240. package/src/server/views/help/accessibility-statement.html +58 -0
  241. package/src/server/views/help/cookie-preferences.html +57 -0
  242. package/src/server/views/help/cookies.html +71 -0
  243. package/src/server/views/help/get-support.html +37 -0
  244. package/src/server/views/help/privacy-notice.html +68 -0
  245. package/src/server/views/help/terms-and-conditions.html +83 -0
  246. package/src/typings/hapi/index.d.ts +87 -0
  247. package/src/typings/hapi-tracing/index.d.ts +6 -0
  248. package/src/typings/index.d.ts +3 -0
  249. package/src/typings/joi/index.d.ts +22 -0
  250. package/.server/server/views/components/service-banner/template.test.js.map +0 -1
  251. package/.server/server/views/components/tag-env/template.test.js.map +0 -1
  252. /package/.server/server/{views → plugins/engine/views}/components/debug/macro.njk +0 -0
  253. /package/.server/server/{views → plugins/engine/views}/components/debug/template.njk +0 -0
  254. /package/.server/server/{views → plugins/engine/views}/components/service-banner/macro.njk +0 -0
  255. /package/.server/server/{views → plugins/engine/views}/components/service-banner/template.njk +0 -0
  256. /package/.server/server/{views → plugins/engine/views}/components/tag-env/macro.njk +0 -0
  257. /package/.server/server/{views → plugins/engine/views}/components/tag-env/template.njk +0 -0
  258. /package/.server/server/{views → plugins/engine/views}/confirmation.html +0 -0
  259. /package/.server/server/{views → plugins/engine/views}/layout.html +0 -0
  260. /package/.server/server/{views → plugins/engine/views}/summary.html +0 -0
@@ -0,0 +1,753 @@
1
+ import { hasFormComponents, slugSchema } from '@defra/forms-model'
2
+ import Boom from '@hapi/boom'
3
+ import {
4
+ type Plugin,
5
+ type ResponseObject,
6
+ type ResponseToolkit,
7
+ type RouteOptions
8
+ } from '@hapi/hapi'
9
+ // import vision from '@hapi/vision'
10
+ import { isEqual } from 'date-fns'
11
+ import Joi from 'joi'
12
+ import { type Environment } from 'nunjucks'
13
+ // import nunjucks, { type Environment } from 'nunjucks'
14
+
15
+ import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
16
+ import {
17
+ checkEmailAddressForLiveFormSubmission,
18
+ checkFormStatus,
19
+ findPage,
20
+ getCacheService,
21
+ getPage,
22
+ getStartPath,
23
+ normalisePath,
24
+ proceed,
25
+ redirectPath
26
+ } from '~/src/server/plugins/engine/helpers.js'
27
+ // import {
28
+ // PLUGIN_PATH,
29
+ // VIEW_PATH,
30
+ // context,
31
+ // prepareNunjucksEnvironment
32
+ // } from '~/src/server/plugins/engine/index.js'
33
+ import {
34
+ FormModel,
35
+ SummaryViewModel
36
+ } from '~/src/server/plugins/engine/models/index.js'
37
+ import { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'
38
+ import { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
39
+ import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
40
+ import { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
41
+ import { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
42
+ import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
43
+ import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
44
+ import { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'
45
+ import {
46
+ type FilterFunction,
47
+ type FormContext
48
+ } from '~/src/server/plugins/engine/types.js'
49
+ import {
50
+ type FormRequest,
51
+ type FormRequestPayload,
52
+ type FormRequestPayloadRefs,
53
+ type FormRequestRefs
54
+ } from '~/src/server/routes/types.js'
55
+ import {
56
+ actionSchema,
57
+ confirmSchema,
58
+ crumbSchema,
59
+ itemIdSchema,
60
+ pathSchema,
61
+ stateSchema
62
+ } from '~/src/server/schemas/index.js'
63
+ import * as httpService from '~/src/server/services/httpService.js'
64
+ import { CacheService } from '~/src/server/services/index.js'
65
+ import { type Services } from '~/src/server/types.js'
66
+
67
+ export interface PluginOptions {
68
+ model?: FormModel
69
+ services?: Services
70
+ controllers?: Record<string, typeof PageController>
71
+ cacheName?: string
72
+ pluginPath?: string
73
+ filters?: Record<string, FilterFunction>
74
+ }
75
+
76
+ export const plugin = {
77
+ name: '@defra/forms-engine-plugin',
78
+ dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],
79
+ multiple: true,
80
+ register(server, options) {
81
+ const {
82
+ model,
83
+ services = defaultServices,
84
+ controllers,
85
+ cacheName
86
+ // pluginPath = PLUGIN_PATH,
87
+ // filters
88
+ } = options
89
+ const { formsService } = services
90
+ const cacheService = new CacheService(server, cacheName)
91
+
92
+ // Paths array to tell `vision` and `nunjucks` where template files are stored.
93
+ // const path = [`${pluginPath}/${VIEW_PATH}`]
94
+
95
+ // await server.register({
96
+ // plugin: vision,
97
+ // options: {
98
+ // engines: {
99
+ // html: {
100
+ // compile: (
101
+ // path: string,
102
+ // compileOptions: { environment: Environment }
103
+ // ) => {
104
+ // const template = nunjucks.compile(
105
+ // path,
106
+ // compileOptions.environment
107
+ // )
108
+
109
+ // return (context: object | undefined) => {
110
+ // return template.render(context)
111
+ // }
112
+ // },
113
+ // prepare: (options: EngineConfigurationObject, next) => {
114
+ // // Nunjucks also needs an additional path configuration
115
+ // // to use the templates and macros from `govuk-frontend`
116
+ // const environment = nunjucks.configure([
117
+ // ...path,
118
+ // 'node_modules/govuk-frontend/dist'
119
+ // ])
120
+
121
+ // // Applies custom filters and globals for nunjucks
122
+ // // that are required by the `forms-engine-plugin`
123
+ // prepareNunjucksEnvironment(environment, filters)
124
+
125
+ // options.compileOptions.environment = environment
126
+
127
+ // next()
128
+ // }
129
+ // }
130
+ // },
131
+ // path,
132
+ // // Provides global context used with all templates
133
+ // context
134
+ // }
135
+ // })
136
+
137
+ server.expose('cacheService', cacheService)
138
+
139
+ server.app.model = model
140
+
141
+ // In-memory cache of FormModel items, exposed
142
+ // (for testing purposes) through `server.app.models`
143
+ const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()
144
+ server.app.models = itemCache
145
+
146
+ const loadFormPreHandler = async (
147
+ request: FormRequest | FormRequestPayload,
148
+ h: Pick<ResponseToolkit, 'continue'>
149
+ ) => {
150
+ if (server.app.model) {
151
+ request.app.model = server.app.model
152
+
153
+ return h.continue
154
+ }
155
+
156
+ const { params, path } = request
157
+ const { slug } = params
158
+ const { isPreview, state: formState } = checkFormStatus(path)
159
+
160
+ // Get the form metadata using the `slug` param
161
+ const metadata = await formsService.getFormMetadata(slug)
162
+
163
+ const { id, [formState]: state } = metadata
164
+
165
+ // Check the metadata supports the requested state
166
+ if (!state) {
167
+ throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)
168
+ }
169
+
170
+ // Cache the models based on id, state and whether
171
+ // it's a preview or not. There could be up to 3 models
172
+ // cached for a single form:
173
+ // "{id}_live_false" (live/live)
174
+ // "{id}_live_true" (live/preview)
175
+ // "{id}_draft_true" (draft/preview)
176
+ const key = `${id}_${formState}_${isPreview}`
177
+ let item = itemCache.get(key)
178
+
179
+ if (!item || !isEqual(item.updatedAt, state.updatedAt)) {
180
+ server.logger.info(
181
+ `Getting form definition ${id} (${slug}) ${formState}`
182
+ )
183
+
184
+ // Get the form definition using the `id` from the metadata
185
+ const definition = await formsService.getFormDefinition(id, formState)
186
+
187
+ if (!definition) {
188
+ throw Boom.notFound(
189
+ `No definition found for form metadata ${id} (${slug}) ${formState}`
190
+ )
191
+ }
192
+
193
+ const emailAddress =
194
+ metadata.notificationEmail ?? definition.outputEmail
195
+
196
+ checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)
197
+
198
+ // Build the form model
199
+ server.logger.info(
200
+ `Building model for form definition ${id} (${slug}) ${formState}`
201
+ )
202
+
203
+ // Set up the basePath for the model
204
+ const basePath = isPreview
205
+ ? `${PREVIEW_PATH_PREFIX.substring(1)}/${formState}/${slug}`
206
+ : slug
207
+
208
+ // Construct the form model
209
+ const model = new FormModel(
210
+ definition,
211
+ { basePath },
212
+ services,
213
+ controllers
214
+ )
215
+
216
+ // Create new item and add it to the item cache
217
+ item = { model, updatedAt: state.updatedAt }
218
+ itemCache.set(key, item)
219
+ }
220
+
221
+ // Assign the model to the request data
222
+ // for use in the downstream handler
223
+ request.app.model = item.model
224
+
225
+ return h.continue
226
+ }
227
+
228
+ const dispatchHandler = (
229
+ request: FormRequest,
230
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
231
+ ) => {
232
+ const { model } = request.app
233
+
234
+ const servicePath = model ? `/${model.basePath}` : ''
235
+ return proceed(request, h, `${servicePath}${getStartPath(model)}`)
236
+ }
237
+
238
+ const redirectOrMakeHandler = async (
239
+ request: FormRequest | FormRequestPayload,
240
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>,
241
+ makeHandler: (
242
+ page: PageControllerClass,
243
+ context: FormContext
244
+ ) => ResponseObject | Promise<ResponseObject>
245
+ ) => {
246
+ const { app, params } = request
247
+ const { model } = app
248
+
249
+ if (!model) {
250
+ throw Boom.notFound(`No model found for /${params.path}`)
251
+ }
252
+
253
+ const cacheService = getCacheService(request.server)
254
+ const page = getPage(model, request)
255
+ const state = await page.getState(request)
256
+ const flash = cacheService.getFlash(request)
257
+ const context = model.getFormContext(request, state, flash?.errors)
258
+ const relevantPath = page.getRelevantPath(request, context)
259
+ const summaryPath = page.getSummaryPath()
260
+
261
+ // Return handler for relevant pages or preview URL direct access
262
+ if (relevantPath.startsWith(page.path) || context.isForceAccess) {
263
+ return makeHandler(page, context)
264
+ }
265
+
266
+ // Redirect back to last relevant page
267
+ const redirectTo = findPage(model, relevantPath)
268
+
269
+ // Set the return URL unless an exit page
270
+ if (redirectTo?.next.length) {
271
+ request.query.returnUrl = page.getHref(summaryPath)
272
+ }
273
+
274
+ return proceed(request, h, page.getHref(relevantPath))
275
+ }
276
+
277
+ const getHandler = (
278
+ request: FormRequest,
279
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
280
+ ) => {
281
+ const { params } = request
282
+
283
+ if (normalisePath(params.path) === '') {
284
+ return dispatchHandler(request, h)
285
+ }
286
+
287
+ return redirectOrMakeHandler(request, h, async (page, context) => {
288
+ // Check for a page onLoad HTTP event and if one exists,
289
+ // call it and assign the response to the context data
290
+ const { events } = page
291
+ const { model } = request.app
292
+
293
+ if (!model) {
294
+ throw Boom.notFound(`No model found for /${params.path}`)
295
+ }
296
+
297
+ if (events?.onLoad && events.onLoad.type === 'http') {
298
+ const { options } = events.onLoad
299
+ const { url } = options
300
+
301
+ // TODO: Update structured data POST payload with when helper
302
+ // is updated to removing the dependency on `SummaryViewModel` etc.
303
+ const viewModel = new SummaryViewModel(request, page, context)
304
+ const items = getFormSubmissionData(
305
+ viewModel.context,
306
+ viewModel.details
307
+ )
308
+
309
+ // @ts-expect-error - function signature will be refactored in the next iteration of the formatter
310
+ const payload = format(items, model, undefined, undefined)
311
+
312
+ const { payload: response } = await httpService.postJson(url, {
313
+ payload
314
+ })
315
+
316
+ Object.assign(context.data, response)
317
+ }
318
+
319
+ return page.makeGetRouteHandler()(request, context, h)
320
+ })
321
+ }
322
+
323
+ const postHandler = (
324
+ request: FormRequestPayload,
325
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
326
+ ) => {
327
+ const { query } = request
328
+
329
+ return redirectOrMakeHandler(request, h, (page, context) => {
330
+ const { pageDef } = page
331
+ const { isForceAccess } = context
332
+
333
+ // Redirect to GET for preview URL direct access
334
+ if (isForceAccess && !hasFormComponents(pageDef)) {
335
+ return proceed(request, h, redirectPath(page.href, query))
336
+ }
337
+
338
+ return page.makePostRouteHandler()(request, context, h)
339
+ })
340
+ }
341
+
342
+ const dispatchRouteOptions: RouteOptions<FormRequestRefs> = {
343
+ pre: [
344
+ {
345
+ method: loadFormPreHandler
346
+ }
347
+ ]
348
+ }
349
+
350
+ server.route({
351
+ method: 'get',
352
+ path: '/{slug}',
353
+ handler: dispatchHandler,
354
+ options: {
355
+ ...dispatchRouteOptions,
356
+ validate: {
357
+ params: Joi.object().keys({
358
+ slug: slugSchema
359
+ })
360
+ }
361
+ }
362
+ })
363
+
364
+ server.route({
365
+ method: 'get',
366
+ path: '/preview/{state}/{slug}',
367
+ handler: dispatchHandler,
368
+ options: {
369
+ ...dispatchRouteOptions,
370
+ validate: {
371
+ params: Joi.object().keys({
372
+ state: stateSchema,
373
+ slug: slugSchema
374
+ })
375
+ }
376
+ }
377
+ })
378
+
379
+ const getRouteOptions: RouteOptions<FormRequestRefs> = {
380
+ pre: [
381
+ {
382
+ method: loadFormPreHandler
383
+ }
384
+ ]
385
+ }
386
+
387
+ server.route({
388
+ method: 'get',
389
+ path: '/{slug}/{path}/{itemId?}',
390
+ handler: getHandler,
391
+ options: {
392
+ ...getRouteOptions,
393
+ validate: {
394
+ params: Joi.object().keys({
395
+ slug: slugSchema,
396
+ path: pathSchema,
397
+ itemId: itemIdSchema.optional()
398
+ })
399
+ }
400
+ }
401
+ })
402
+
403
+ server.route({
404
+ method: 'get',
405
+ path: '/preview/{state}/{slug}/{path}/{itemId?}',
406
+ handler: getHandler,
407
+ options: {
408
+ ...getRouteOptions,
409
+ validate: {
410
+ params: Joi.object().keys({
411
+ state: stateSchema,
412
+ slug: slugSchema,
413
+ path: pathSchema,
414
+ itemId: itemIdSchema.optional()
415
+ })
416
+ }
417
+ }
418
+ })
419
+
420
+ const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {
421
+ payload: {
422
+ parse: true
423
+ },
424
+ pre: [{ method: loadFormPreHandler }]
425
+ }
426
+
427
+ server.route({
428
+ method: 'post',
429
+ path: '/{slug}/{path}/{itemId?}',
430
+ handler: postHandler,
431
+ options: {
432
+ ...postRouteOptions,
433
+ validate: {
434
+ params: Joi.object().keys({
435
+ slug: slugSchema,
436
+ path: pathSchema,
437
+ itemId: itemIdSchema.optional()
438
+ }),
439
+ payload: Joi.object()
440
+ .keys({
441
+ crumb: crumbSchema,
442
+ action: actionSchema
443
+ })
444
+ .unknown(true)
445
+ .required()
446
+ }
447
+ }
448
+ })
449
+
450
+ server.route({
451
+ method: 'post',
452
+ path: '/preview/{state}/{slug}/{path}/{itemId?}',
453
+ handler: postHandler,
454
+ options: {
455
+ ...postRouteOptions,
456
+ validate: {
457
+ params: Joi.object().keys({
458
+ state: stateSchema,
459
+ slug: slugSchema,
460
+ path: pathSchema,
461
+ itemId: itemIdSchema.optional()
462
+ }),
463
+ payload: Joi.object()
464
+ .keys({
465
+ crumb: crumbSchema,
466
+ action: actionSchema
467
+ })
468
+ .unknown(true)
469
+ .required()
470
+ }
471
+ }
472
+ })
473
+
474
+ /**
475
+ * "AddAnother" repeat routes
476
+ */
477
+
478
+ // List summary GET route
479
+ const getListSummaryHandler = (
480
+ request: FormRequest,
481
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
482
+ ) => {
483
+ const { params } = request
484
+
485
+ return redirectOrMakeHandler(request, h, (page, context) => {
486
+ if (!(page instanceof RepeatPageController)) {
487
+ throw Boom.notFound(`No repeater page found for /${params.path}`)
488
+ }
489
+
490
+ return page.makeGetListSummaryRouteHandler()(request, context, h)
491
+ })
492
+ }
493
+
494
+ server.route({
495
+ method: 'get',
496
+ path: '/{slug}/{path}/summary',
497
+ handler: getListSummaryHandler,
498
+ options: {
499
+ ...getRouteOptions,
500
+ validate: {
501
+ params: Joi.object().keys({
502
+ slug: slugSchema,
503
+ path: pathSchema
504
+ })
505
+ }
506
+ }
507
+ })
508
+
509
+ server.route({
510
+ method: 'get',
511
+ path: '/preview/{state}/{slug}/{path}/summary',
512
+ handler: getListSummaryHandler,
513
+ options: {
514
+ ...getRouteOptions,
515
+ validate: {
516
+ params: Joi.object().keys({
517
+ state: stateSchema,
518
+ slug: slugSchema,
519
+ path: pathSchema
520
+ })
521
+ }
522
+ }
523
+ })
524
+
525
+ // List summary POST route
526
+ const postListSummaryHandler = (
527
+ request: FormRequestPayload,
528
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
529
+ ) => {
530
+ const { params } = request
531
+
532
+ return redirectOrMakeHandler(request, h, (page, context) => {
533
+ const { isForceAccess } = context
534
+
535
+ if (isForceAccess || !(page instanceof RepeatPageController)) {
536
+ throw Boom.notFound(`No repeater page found for /${params.path}`)
537
+ }
538
+
539
+ return page.makePostListSummaryRouteHandler()(request, context, h)
540
+ })
541
+ }
542
+
543
+ server.route({
544
+ method: 'post',
545
+ path: '/{slug}/{path}/summary',
546
+ handler: postListSummaryHandler,
547
+ options: {
548
+ ...postRouteOptions,
549
+ validate: {
550
+ params: Joi.object().keys({
551
+ slug: slugSchema,
552
+ path: pathSchema
553
+ }),
554
+ payload: Joi.object()
555
+ .keys({
556
+ crumb: crumbSchema,
557
+ action: actionSchema
558
+ })
559
+ .required()
560
+ }
561
+ }
562
+ })
563
+
564
+ server.route({
565
+ method: 'post',
566
+ path: '/preview/{state}/{slug}/{path}/summary',
567
+ handler: postListSummaryHandler,
568
+ options: {
569
+ ...postRouteOptions,
570
+ validate: {
571
+ params: Joi.object().keys({
572
+ state: stateSchema,
573
+ slug: slugSchema,
574
+ path: pathSchema
575
+ }),
576
+ payload: Joi.object()
577
+ .keys({
578
+ crumb: crumbSchema,
579
+ action: actionSchema
580
+ })
581
+ .required()
582
+ }
583
+ }
584
+ })
585
+
586
+ // Item delete GET route
587
+ const getItemDeleteHandler = (
588
+ request: FormRequest,
589
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
590
+ ) => {
591
+ const { params } = request
592
+
593
+ return redirectOrMakeHandler(request, h, (page, context) => {
594
+ if (
595
+ !(
596
+ page instanceof RepeatPageController ||
597
+ page instanceof FileUploadPageController
598
+ )
599
+ ) {
600
+ throw Boom.notFound(`No page found for /${params.path}`)
601
+ }
602
+
603
+ return page.makeGetItemDeleteRouteHandler()(request, context, h)
604
+ })
605
+ }
606
+
607
+ server.route({
608
+ method: 'get',
609
+ path: '/{slug}/{path}/{itemId}/confirm-delete',
610
+ handler: getItemDeleteHandler,
611
+ options: {
612
+ ...getRouteOptions,
613
+ validate: {
614
+ params: Joi.object().keys({
615
+ slug: slugSchema,
616
+ path: pathSchema,
617
+ itemId: itemIdSchema
618
+ })
619
+ }
620
+ }
621
+ })
622
+
623
+ server.route({
624
+ method: 'get',
625
+ path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',
626
+ handler: getItemDeleteHandler,
627
+ options: {
628
+ ...getRouteOptions,
629
+ validate: {
630
+ params: Joi.object().keys({
631
+ state: stateSchema,
632
+ slug: slugSchema,
633
+ path: pathSchema,
634
+ itemId: itemIdSchema
635
+ })
636
+ }
637
+ }
638
+ })
639
+
640
+ // Item delete POST route
641
+ const postItemDeleteHandler = (
642
+ request: FormRequestPayload,
643
+ h: Pick<ResponseToolkit, 'redirect' | 'view'>
644
+ ) => {
645
+ const { params } = request
646
+
647
+ return redirectOrMakeHandler(request, h, (page, context) => {
648
+ const { isForceAccess } = context
649
+
650
+ if (
651
+ isForceAccess ||
652
+ !(
653
+ page instanceof RepeatPageController ||
654
+ page instanceof FileUploadPageController
655
+ )
656
+ ) {
657
+ throw Boom.notFound(`No page found for /${params.path}`)
658
+ }
659
+
660
+ return page.makePostItemDeleteRouteHandler()(request, context, h)
661
+ })
662
+ }
663
+
664
+ server.route({
665
+ method: 'post',
666
+ path: '/{slug}/{path}/{itemId}/confirm-delete',
667
+ handler: postItemDeleteHandler,
668
+ options: {
669
+ ...postRouteOptions,
670
+ validate: {
671
+ params: Joi.object().keys({
672
+ slug: slugSchema,
673
+ path: pathSchema,
674
+ itemId: itemIdSchema
675
+ }),
676
+ payload: Joi.object()
677
+ .keys({
678
+ crumb: crumbSchema,
679
+ action: actionSchema,
680
+ confirm: confirmSchema
681
+ })
682
+ .required()
683
+ }
684
+ }
685
+ })
686
+
687
+ server.route({
688
+ method: 'post',
689
+ path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',
690
+ handler: postItemDeleteHandler,
691
+ options: {
692
+ ...postRouteOptions,
693
+ validate: {
694
+ params: Joi.object().keys({
695
+ state: stateSchema,
696
+ slug: slugSchema,
697
+ path: pathSchema,
698
+ itemId: itemIdSchema
699
+ }),
700
+ payload: Joi.object()
701
+ .keys({
702
+ crumb: crumbSchema,
703
+ action: actionSchema,
704
+ confirm: confirmSchema
705
+ })
706
+ .required()
707
+ }
708
+ }
709
+ })
710
+
711
+ server.route({
712
+ method: 'get',
713
+ path: '/upload-status/{uploadId}',
714
+ handler: async (request, h) => {
715
+ try {
716
+ const { uploadId } = request.params as { uploadId: string }
717
+ const status = await getUploadStatus(uploadId)
718
+
719
+ if (!status) {
720
+ return h.response({ error: 'Status check failed' }).code(400)
721
+ }
722
+
723
+ return h.response(status)
724
+ } catch (error) {
725
+ request.logger.error(
726
+ ['upload-status'],
727
+ 'Upload status check failed',
728
+ error
729
+ )
730
+ return h.response({ error: 'Status check error' }).code(500)
731
+ }
732
+ },
733
+ options: {
734
+ plugins: {
735
+ crumb: false
736
+ },
737
+ validate: {
738
+ params: Joi.object().keys({
739
+ uploadId: Joi.string().guid().required()
740
+ })
741
+ }
742
+ }
743
+ })
744
+ }
745
+ } satisfies Plugin<PluginOptions>
746
+
747
+ interface CompileOptions {
748
+ environment: Environment
749
+ }
750
+
751
+ export interface EngineConfigurationObject {
752
+ compileOptions: CompileOptions
753
+ }