@defra/forms-engine-plugin 0.0.1

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 (615) hide show
  1. package/.browserslistrc +16 -0
  2. package/.editorconfig +9 -0
  3. package/.eslintrc.cjs +266 -0
  4. package/.github/dependabot.yml +85 -0
  5. package/.github/workflows/check-pull-request.yml +209 -0
  6. package/.github/workflows/pr-notifier.yml +98 -0
  7. package/.github/workflows/publish.yml +111 -0
  8. package/.husky/pre-commit +1 -0
  9. package/.lintstagedrc.js +4 -0
  10. package/.nvmrc +1 -0
  11. package/.prettierignore +8 -0
  12. package/.prettierrc.cjs +26 -0
  13. package/.server/client/javascripts/application.js +68 -0
  14. package/.server/client/javascripts/application.js.map +1 -0
  15. package/.server/client/javascripts/file-upload.js +292 -0
  16. package/.server/client/javascripts/file-upload.js.map +1 -0
  17. package/.server/client/stylesheets/_code.scss +33 -0
  18. package/.server/client/stylesheets/_govuk-frontend.scss +4 -0
  19. package/.server/client/stylesheets/_prose.scss +56 -0
  20. package/.server/client/stylesheets/_service-banner.scss +24 -0
  21. package/.server/client/stylesheets/_summary-list.scss +28 -0
  22. package/.server/client/stylesheets/_tag-env.scss +24 -0
  23. package/.server/client/stylesheets/application.scss +14 -0
  24. package/.server/common/cookies.js +55 -0
  25. package/.server/common/cookies.js.map +1 -0
  26. package/.server/common/cookies.test.js +15 -0
  27. package/.server/common/cookies.test.js.map +1 -0
  28. package/.server/common/types.js +6 -0
  29. package/.server/common/types.js.map +1 -0
  30. package/.server/config/index.js +241 -0
  31. package/.server/config/index.js.map +1 -0
  32. package/.server/index.js +25 -0
  33. package/.server/index.js.map +1 -0
  34. package/.server/server/common/helpers/logging/logger-options.js +44 -0
  35. package/.server/server/common/helpers/logging/logger-options.js.map +1 -0
  36. package/.server/server/common/helpers/logging/logger.js +6 -0
  37. package/.server/server/common/helpers/logging/logger.js.map +1 -0
  38. package/.server/server/common/helpers/logging/request-logger.js +7 -0
  39. package/.server/server/common/helpers/logging/request-logger.js.map +1 -0
  40. package/.server/server/common/helpers/logging/request-tracing.js +12 -0
  41. package/.server/server/common/helpers/logging/request-tracing.js.map +1 -0
  42. package/.server/server/common/helpers/redis-client.js +55 -0
  43. package/.server/server/common/helpers/redis-client.js.map +1 -0
  44. package/.server/server/constants.js +2 -0
  45. package/.server/server/constants.js.map +1 -0
  46. package/.server/server/forms/README.md +10 -0
  47. package/.server/server/forms/components.json +1015 -0
  48. package/.server/server/forms/report-a-terrorist.json +270 -0
  49. package/.server/server/forms/runner-components-test.json +365 -0
  50. package/.server/server/forms/test.json +581 -0
  51. package/.server/server/index.js +116 -0
  52. package/.server/server/index.js.map +1 -0
  53. package/.server/server/plugins/blankie.js +29 -0
  54. package/.server/server/plugins/blankie.js.map +1 -0
  55. package/.server/server/plugins/crumb.js +15 -0
  56. package/.server/server/plugins/crumb.js.map +1 -0
  57. package/.server/server/plugins/engine/README.md +87 -0
  58. package/.server/server/plugins/engine/components/AutocompleteField.js +37 -0
  59. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -0
  60. package/.server/server/plugins/engine/components/CheckboxesField.js +79 -0
  61. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -0
  62. package/.server/server/plugins/engine/components/ComponentBase.js +55 -0
  63. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -0
  64. package/.server/server/plugins/engine/components/ComponentCollection.js +198 -0
  65. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -0
  66. package/.server/server/plugins/engine/components/DatePartsField.js +208 -0
  67. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -0
  68. package/.server/server/plugins/engine/components/Details.js +26 -0
  69. package/.server/server/plugins/engine/components/Details.js.map +1 -0
  70. package/.server/server/plugins/engine/components/EmailAddressField.js +40 -0
  71. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -0
  72. package/.server/server/plugins/engine/components/FileUploadField.js +195 -0
  73. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -0
  74. package/.server/server/plugins/engine/components/FormComponent.js +210 -0
  75. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -0
  76. package/.server/server/plugins/engine/components/Html.js +24 -0
  77. package/.server/server/plugins/engine/components/Html.js.map +1 -0
  78. package/.server/server/plugins/engine/components/InsetText.js +22 -0
  79. package/.server/server/plugins/engine/components/InsetText.js.map +1 -0
  80. package/.server/server/plugins/engine/components/List.js +65 -0
  81. package/.server/server/plugins/engine/components/List.js.map +1 -0
  82. package/.server/server/plugins/engine/components/ListFormComponent.js +87 -0
  83. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -0
  84. package/.server/server/plugins/engine/components/MonthYearField.js +172 -0
  85. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -0
  86. package/.server/server/plugins/engine/components/MultilineTextField.js +115 -0
  87. package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -0
  88. package/.server/server/plugins/engine/components/NumberField.js +137 -0
  89. package/.server/server/plugins/engine/components/NumberField.js.map +1 -0
  90. package/.server/server/plugins/engine/components/RadiosField.js +18 -0
  91. package/.server/server/plugins/engine/components/RadiosField.js.map +1 -0
  92. package/.server/server/plugins/engine/components/SelectField.js +33 -0
  93. package/.server/server/plugins/engine/components/SelectField.js.map +1 -0
  94. package/.server/server/plugins/engine/components/SelectionControlField.js +43 -0
  95. package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -0
  96. package/.server/server/plugins/engine/components/TelephoneNumberField.js +43 -0
  97. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -0
  98. package/.server/server/plugins/engine/components/TextField.js +63 -0
  99. package/.server/server/plugins/engine/components/TextField.js.map +1 -0
  100. package/.server/server/plugins/engine/components/UkAddressField.js +130 -0
  101. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -0
  102. package/.server/server/plugins/engine/components/YesNoField.js +28 -0
  103. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -0
  104. package/.server/server/plugins/engine/components/constants.js +2 -0
  105. package/.server/server/plugins/engine/components/constants.js.map +1 -0
  106. package/.server/server/plugins/engine/components/helpers.js +219 -0
  107. package/.server/server/plugins/engine/components/helpers.js.map +1 -0
  108. package/.server/server/plugins/engine/components/index.js +25 -0
  109. package/.server/server/plugins/engine/components/index.js.map +1 -0
  110. package/.server/server/plugins/engine/components/types.js +2 -0
  111. package/.server/server/plugins/engine/components/types.js.map +1 -0
  112. package/.server/server/plugins/engine/configureEnginePlugin.js +44 -0
  113. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -0
  114. package/.server/server/plugins/engine/helpers.js +268 -0
  115. package/.server/server/plugins/engine/helpers.js.map +1 -0
  116. package/.server/server/plugins/engine/index.js +6 -0
  117. package/.server/server/plugins/engine/index.js.map +1 -0
  118. package/.server/server/plugins/engine/models/FormModel.js +363 -0
  119. package/.server/server/plugins/engine/models/FormModel.js.map +1 -0
  120. package/.server/server/plugins/engine/models/RepeatingSummaryViewModel.js +2 -0
  121. package/.server/server/plugins/engine/models/RepeatingSummaryViewModel.js.map +1 -0
  122. package/.server/server/plugins/engine/models/Section.js +2 -0
  123. package/.server/server/plugins/engine/models/Section.js.map +1 -0
  124. package/.server/server/plugins/engine/models/SummaryViewModel.js +183 -0
  125. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -0
  126. package/.server/server/plugins/engine/models/index.js +3 -0
  127. package/.server/server/plugins/engine/models/index.js.map +1 -0
  128. package/.server/server/plugins/engine/models/types.js +2 -0
  129. package/.server/server/plugins/engine/models/types.js.map +1 -0
  130. package/.server/server/plugins/engine/outputFormatters/human/v1.js +44 -0
  131. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -0
  132. package/.server/server/plugins/engine/outputFormatters/index.js +24 -0
  133. package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -0
  134. package/.server/server/plugins/engine/outputFormatters/machine/v1.js +104 -0
  135. package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -0
  136. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +110 -0
  137. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -0
  138. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +401 -0
  139. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -0
  140. package/.server/server/plugins/engine/pageControllers/PageController.js +133 -0
  141. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -0
  142. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +482 -0
  143. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -0
  144. package/.server/server/plugins/engine/pageControllers/README.md +28 -0
  145. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +372 -0
  146. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -0
  147. package/.server/server/plugins/engine/pageControllers/StartPageController.js +16 -0
  148. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -0
  149. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +43 -0
  150. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -0
  151. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +195 -0
  152. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -0
  153. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +8 -0
  154. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -0
  155. package/.server/server/plugins/engine/pageControllers/helpers.js +69 -0
  156. package/.server/server/plugins/engine/pageControllers/helpers.js.map +1 -0
  157. package/.server/server/plugins/engine/pageControllers/index.js +10 -0
  158. package/.server/server/plugins/engine/pageControllers/index.js.map +1 -0
  159. package/.server/server/plugins/engine/pageControllers/validationOptions.js +78 -0
  160. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -0
  161. package/.server/server/plugins/engine/plugin.js +562 -0
  162. package/.server/server/plugins/engine/plugin.js.map +1 -0
  163. package/.server/server/plugins/engine/services/formSubmissionService.js +39 -0
  164. package/.server/server/plugins/engine/services/formSubmissionService.js.map +1 -0
  165. package/.server/server/plugins/engine/services/formsService.js +41 -0
  166. package/.server/server/plugins/engine/services/formsService.js.map +1 -0
  167. package/.server/server/plugins/engine/services/formsService.test.js +71 -0
  168. package/.server/server/plugins/engine/services/formsService.test.js.map +1 -0
  169. package/.server/server/plugins/engine/services/index.js +4 -0
  170. package/.server/server/plugins/engine/services/index.js.map +1 -0
  171. package/.server/server/plugins/engine/services/notifyService.js +45 -0
  172. package/.server/server/plugins/engine/services/notifyService.js.map +1 -0
  173. package/.server/server/plugins/engine/services/uploadService.js +51 -0
  174. package/.server/server/plugins/engine/services/uploadService.js.map +1 -0
  175. package/.server/server/plugins/engine/types.js +53 -0
  176. package/.server/server/plugins/engine/types.js.map +1 -0
  177. package/.server/server/plugins/engine/views/components/autocompletefield.html +5 -0
  178. package/.server/server/plugins/engine/views/components/checkboxesfield.html +5 -0
  179. package/.server/server/plugins/engine/views/components/datepartsfield.html +5 -0
  180. package/.server/server/plugins/engine/views/components/details.html +6 -0
  181. package/.server/server/plugins/engine/views/components/emailaddressfield.html +5 -0
  182. package/.server/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
  183. package/.server/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
  184. package/.server/server/plugins/engine/views/components/fileuploadfield.html +24 -0
  185. package/.server/server/plugins/engine/views/components/html.html +3 -0
  186. package/.server/server/plugins/engine/views/components/insettext.html +7 -0
  187. package/.server/server/plugins/engine/views/components/list.html +36 -0
  188. package/.server/server/plugins/engine/views/components/monthyearfield.html +5 -0
  189. package/.server/server/plugins/engine/views/components/multilinetextfield.html +10 -0
  190. package/.server/server/plugins/engine/views/components/numberfield.html +5 -0
  191. package/.server/server/plugins/engine/views/components/radiosfield.html +5 -0
  192. package/.server/server/plugins/engine/views/components/selectfield.html +5 -0
  193. package/.server/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
  194. package/.server/server/plugins/engine/views/components/textfield.html +5 -0
  195. package/.server/server/plugins/engine/views/components/ukaddressfield.html +25 -0
  196. package/.server/server/plugins/engine/views/components/yesnofield.html +5 -0
  197. package/.server/server/plugins/engine/views/file-upload.html +45 -0
  198. package/.server/server/plugins/engine/views/index.html +39 -0
  199. package/.server/server/plugins/engine/views/item-delete.html +56 -0
  200. package/.server/server/plugins/engine/views/partials/components.html +6 -0
  201. package/.server/server/plugins/engine/views/partials/conditional-components.html +3 -0
  202. package/.server/server/plugins/engine/views/partials/debug.html +44 -0
  203. package/.server/server/plugins/engine/views/partials/form.html +15 -0
  204. package/.server/server/plugins/engine/views/partials/heading.html +16 -0
  205. package/.server/server/plugins/engine/views/partials/preview-banner.html +32 -0
  206. package/.server/server/plugins/engine/views/partials/preview-banner.test.js +108 -0
  207. package/.server/server/plugins/engine/views/partials/preview-banner.test.js.map +1 -0
  208. package/.server/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
  209. package/.server/server/plugins/engine/views/repeat-list-summary.html +53 -0
  210. package/.server/server/plugins/errorPages.js +49 -0
  211. package/.server/server/plugins/errorPages.js.map +1 -0
  212. package/.server/server/plugins/nunjucks/context.js +79 -0
  213. package/.server/server/plugins/nunjucks/context.js.map +1 -0
  214. package/.server/server/plugins/nunjucks/context.test.js +134 -0
  215. package/.server/server/plugins/nunjucks/context.test.js.map +1 -0
  216. package/.server/server/plugins/nunjucks/enviroment.test.js +166 -0
  217. package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -0
  218. package/.server/server/plugins/nunjucks/environment.js +95 -0
  219. package/.server/server/plugins/nunjucks/environment.js.map +1 -0
  220. package/.server/server/plugins/nunjucks/filters/answer.js +26 -0
  221. package/.server/server/plugins/nunjucks/filters/answer.js.map +1 -0
  222. package/.server/server/plugins/nunjucks/filters/answer.test.js +70 -0
  223. package/.server/server/plugins/nunjucks/filters/answer.test.js.map +1 -0
  224. package/.server/server/plugins/nunjucks/filters/evaluate.js +22 -0
  225. package/.server/server/plugins/nunjucks/filters/evaluate.js.map +1 -0
  226. package/.server/server/plugins/nunjucks/filters/field.js +26 -0
  227. package/.server/server/plugins/nunjucks/filters/field.js.map +1 -0
  228. package/.server/server/plugins/nunjucks/filters/field.test.js +64 -0
  229. package/.server/server/plugins/nunjucks/filters/field.test.js.map +1 -0
  230. package/.server/server/plugins/nunjucks/filters/highlight.js +14 -0
  231. package/.server/server/plugins/nunjucks/filters/highlight.js.map +1 -0
  232. package/.server/server/plugins/nunjucks/filters/href.js +28 -0
  233. package/.server/server/plugins/nunjucks/filters/href.js.map +1 -0
  234. package/.server/server/plugins/nunjucks/filters/href.test.js +63 -0
  235. package/.server/server/plugins/nunjucks/filters/href.test.js.map +1 -0
  236. package/.server/server/plugins/nunjucks/filters/index.js +9 -0
  237. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -0
  238. package/.server/server/plugins/nunjucks/filters/inspect.js +16 -0
  239. package/.server/server/plugins/nunjucks/filters/inspect.js.map +1 -0
  240. package/.server/server/plugins/nunjucks/filters/page.js +23 -0
  241. package/.server/server/plugins/nunjucks/filters/page.js.map +1 -0
  242. package/.server/server/plugins/nunjucks/filters/page.test.js +51 -0
  243. package/.server/server/plugins/nunjucks/filters/page.test.js.map +1 -0
  244. package/.server/server/plugins/nunjucks/index.js +4 -0
  245. package/.server/server/plugins/nunjucks/index.js.map +1 -0
  246. package/.server/server/plugins/nunjucks/plugin.js +38 -0
  247. package/.server/server/plugins/nunjucks/plugin.js.map +1 -0
  248. package/.server/server/plugins/nunjucks/render.js +41 -0
  249. package/.server/server/plugins/nunjucks/render.js.map +1 -0
  250. package/.server/server/plugins/nunjucks/types.js +41 -0
  251. package/.server/server/plugins/nunjucks/types.js.map +1 -0
  252. package/.server/server/plugins/pulse.js +8 -0
  253. package/.server/server/plugins/pulse.js.map +1 -0
  254. package/.server/server/plugins/router.js +169 -0
  255. package/.server/server/plugins/router.js.map +1 -0
  256. package/.server/server/plugins/session.js +28 -0
  257. package/.server/server/plugins/session.js.map +1 -0
  258. package/.server/server/routes/health.js +15 -0
  259. package/.server/server/routes/health.js.map +1 -0
  260. package/.server/server/routes/health.test.js +32 -0
  261. package/.server/server/routes/health.test.js.map +1 -0
  262. package/.server/server/routes/index.js +3 -0
  263. package/.server/server/routes/index.js.map +1 -0
  264. package/.server/server/routes/public.js +37 -0
  265. package/.server/server/routes/public.js.map +1 -0
  266. package/.server/server/routes/types.js +14 -0
  267. package/.server/server/routes/types.js.map +1 -0
  268. package/.server/server/schemas/index.js +15 -0
  269. package/.server/server/schemas/index.js.map +1 -0
  270. package/.server/server/secure-context.js +36 -0
  271. package/.server/server/secure-context.js.map +1 -0
  272. package/.server/server/services/cacheService.js +85 -0
  273. package/.server/server/services/cacheService.js.map +1 -0
  274. package/.server/server/services/httpService.js +44 -0
  275. package/.server/server/services/httpService.js.map +1 -0
  276. package/.server/server/services/httpService.test.js +504 -0
  277. package/.server/server/services/httpService.test.js.map +1 -0
  278. package/.server/server/services/index.js +2 -0
  279. package/.server/server/services/index.js.map +1 -0
  280. package/.server/server/types.js +2 -0
  281. package/.server/server/types.js.map +1 -0
  282. package/.server/server/utils/notify.js +40 -0
  283. package/.server/server/utils/notify.js.map +1 -0
  284. package/.server/server/utils/secure-context/get-trust-store-certs.js +7 -0
  285. package/.server/server/utils/secure-context/get-trust-store-certs.js.map +1 -0
  286. package/.server/server/utils/secure-context/get-trust-store-certs.test.js +14 -0
  287. package/.server/server/utils/secure-context/get-trust-store-certs.test.js.map +1 -0
  288. package/.server/server/utils/utils.js +20 -0
  289. package/.server/server/utils/utils.js.map +1 -0
  290. package/.server/server/utils/utils.test.js +49 -0
  291. package/.server/server/utils/utils.test.js.map +1 -0
  292. package/.server/server/views/404.html +16 -0
  293. package/.server/server/views/500.html +19 -0
  294. package/.server/server/views/components/debug/macro.njk +3 -0
  295. package/.server/server/views/components/debug/template.njk +13 -0
  296. package/.server/server/views/components/service-banner/macro.njk +3 -0
  297. package/.server/server/views/components/service-banner/template.njk +20 -0
  298. package/.server/server/views/components/service-banner/template.test.js +36 -0
  299. package/.server/server/views/components/service-banner/template.test.js.map +1 -0
  300. package/.server/server/views/components/tag-env/macro.njk +3 -0
  301. package/.server/server/views/components/tag-env/template.njk +30 -0
  302. package/.server/server/views/components/tag-env/template.test.js +59 -0
  303. package/.server/server/views/components/tag-env/template.test.js.map +1 -0
  304. package/.server/server/views/confirmation.html +19 -0
  305. package/.server/server/views/help/accessibility-statement.html +58 -0
  306. package/.server/server/views/help/cookie-preferences.html +57 -0
  307. package/.server/server/views/help/cookies.html +71 -0
  308. package/.server/server/views/help/get-support.html +37 -0
  309. package/.server/server/views/help/privacy-notice.html +68 -0
  310. package/.server/server/views/help/terms-and-conditions.html +83 -0
  311. package/.server/server/views/layout.html +199 -0
  312. package/.server/server/views/summary.html +50 -0
  313. package/.server/typings/hapi/index.d.js +2 -0
  314. package/.server/typings/hapi/index.d.js.map +1 -0
  315. package/.server/typings/hapi-tracing/index.d.js +2 -0
  316. package/.server/typings/hapi-tracing/index.d.js.map +1 -0
  317. package/.server/typings/index.d.js +2 -0
  318. package/.server/typings/index.d.js.map +1 -0
  319. package/.server/typings/joi/index.d.js +2 -0
  320. package/.server/typings/joi/index.d.js.map +1 -0
  321. package/Dockerfile +61 -0
  322. package/LICENCE +8 -0
  323. package/Procfile +1 -0
  324. package/README.md +87 -0
  325. package/babel.config.cjs +55 -0
  326. package/compose/aws.env +4 -0
  327. package/compose/start-localstack.sh +26 -0
  328. package/docker-compose.yaml +86 -0
  329. package/globals.d.ts +1 -0
  330. package/jest.config.cjs +54 -0
  331. package/jest.environment.js +4 -0
  332. package/jest.setup.cjs +14 -0
  333. package/package.json +163 -0
  334. package/postcss.config.js +26 -0
  335. package/sonar-project.properties +17 -0
  336. package/src/client/javascripts/application.js +87 -0
  337. package/src/client/javascripts/file-upload.js +386 -0
  338. package/src/client/stylesheets/_code.scss +33 -0
  339. package/src/client/stylesheets/_govuk-frontend.scss +4 -0
  340. package/src/client/stylesheets/_prose.scss +56 -0
  341. package/src/client/stylesheets/_service-banner.scss +24 -0
  342. package/src/client/stylesheets/_summary-list.scss +28 -0
  343. package/src/client/stylesheets/_tag-env.scss +24 -0
  344. package/src/client/stylesheets/application.scss +14 -0
  345. package/src/common/cookies.js +58 -0
  346. package/src/common/cookies.test.js +23 -0
  347. package/src/common/types.js +5 -0
  348. package/src/config/index.ts +271 -0
  349. package/src/index.ts +31 -0
  350. package/src/server/common/helpers/logging/logger-options.test.ts +50 -0
  351. package/src/server/common/helpers/logging/logger-options.ts +46 -0
  352. package/src/server/common/helpers/logging/logger.ts +7 -0
  353. package/src/server/common/helpers/logging/request-logger.ts +9 -0
  354. package/src/server/common/helpers/logging/request-tracing.js +10 -0
  355. package/src/server/common/helpers/redis-client.js +70 -0
  356. package/src/server/constants.js +1 -0
  357. package/src/server/forms/README.md +10 -0
  358. package/src/server/forms/components.json +1015 -0
  359. package/src/server/forms/report-a-terrorist.json +270 -0
  360. package/src/server/forms/runner-components-test.json +365 -0
  361. package/src/server/forms/test.json +581 -0
  362. package/src/server/index.test.ts +582 -0
  363. package/src/server/index.ts +140 -0
  364. package/src/server/plugins/blankie.test.ts +73 -0
  365. package/src/server/plugins/blankie.ts +48 -0
  366. package/src/server/plugins/crumb.ts +20 -0
  367. package/src/server/plugins/engine/README.md +87 -0
  368. package/src/server/plugins/engine/components/AutocompleteField.test.ts +294 -0
  369. package/src/server/plugins/engine/components/AutocompleteField.ts +49 -0
  370. package/src/server/plugins/engine/components/CheckboxesField.test.ts +379 -0
  371. package/src/server/plugins/engine/components/CheckboxesField.ts +106 -0
  372. package/src/server/plugins/engine/components/ComponentBase.ts +97 -0
  373. package/src/server/plugins/engine/components/ComponentCollection.ts +278 -0
  374. package/src/server/plugins/engine/components/DatePartsField.test.ts +822 -0
  375. package/src/server/plugins/engine/components/DatePartsField.ts +264 -0
  376. package/src/server/plugins/engine/components/Details.test.ts +49 -0
  377. package/src/server/plugins/engine/components/Details.ts +30 -0
  378. package/src/server/plugins/engine/components/EmailAddressField.test.ts +395 -0
  379. package/src/server/plugins/engine/components/EmailAddressField.ts +55 -0
  380. package/src/server/plugins/engine/components/FileUploadField.test.ts +778 -0
  381. package/src/server/plugins/engine/components/FileUploadField.ts +262 -0
  382. package/src/server/plugins/engine/components/FormComponent.ts +249 -0
  383. package/src/server/plugins/engine/components/Html.test.ts +48 -0
  384. package/src/server/plugins/engine/components/Html.ts +29 -0
  385. package/src/server/plugins/engine/components/InsetText.test.ts +48 -0
  386. package/src/server/plugins/engine/components/InsetText.ts +27 -0
  387. package/src/server/plugins/engine/components/List.test.ts +76 -0
  388. package/src/server/plugins/engine/components/List.ts +72 -0
  389. package/src/server/plugins/engine/components/ListFormComponent.ts +140 -0
  390. package/src/server/plugins/engine/components/MonthYearField.test.ts +567 -0
  391. package/src/server/plugins/engine/components/MonthYearField.ts +222 -0
  392. package/src/server/plugins/engine/components/MultilineTextField.test.ts +558 -0
  393. package/src/server/plugins/engine/components/MultilineTextField.ts +138 -0
  394. package/src/server/plugins/engine/components/NumberField.test.ts +701 -0
  395. package/src/server/plugins/engine/components/NumberField.ts +163 -0
  396. package/src/server/plugins/engine/components/RadiosField.test.ts +288 -0
  397. package/src/server/plugins/engine/components/RadiosField.ts +24 -0
  398. package/src/server/plugins/engine/components/SelectField.test.ts +288 -0
  399. package/src/server/plugins/engine/components/SelectField.ts +47 -0
  400. package/src/server/plugins/engine/components/SelectionControlField.ts +43 -0
  401. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +356 -0
  402. package/src/server/plugins/engine/components/TelephoneNumberField.ts +67 -0
  403. package/src/server/plugins/engine/components/TextField.test.ts +489 -0
  404. package/src/server/plugins/engine/components/TextField.ts +96 -0
  405. package/src/server/plugins/engine/components/UkAddressField.test.ts +623 -0
  406. package/src/server/plugins/engine/components/UkAddressField.ts +172 -0
  407. package/src/server/plugins/engine/components/YesNoField.test.ts +248 -0
  408. package/src/server/plugins/engine/components/YesNoField.ts +31 -0
  409. package/src/server/plugins/engine/components/constants.ts +1 -0
  410. package/src/server/plugins/engine/components/helpers.ts +330 -0
  411. package/src/server/plugins/engine/components/index.ts +24 -0
  412. package/src/server/plugins/engine/components/types.ts +117 -0
  413. package/src/server/plugins/engine/configureEnginePlugin.ts +47 -0
  414. package/src/server/plugins/engine/helpers.test.ts +791 -0
  415. package/src/server/plugins/engine/helpers.ts +379 -0
  416. package/src/server/plugins/engine/index.ts +7 -0
  417. package/src/server/plugins/engine/models/FormModel.test.ts +42 -0
  418. package/src/server/plugins/engine/models/FormModel.ts +443 -0
  419. package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
  420. package/src/server/plugins/engine/models/Section.ts +0 -0
  421. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +209 -0
  422. package/src/server/plugins/engine/models/SummaryViewModel.ts +220 -0
  423. package/src/server/plugins/engine/models/index.ts +2 -0
  424. package/src/server/plugins/engine/models/types.ts +114 -0
  425. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +143 -0
  426. package/src/server/plugins/engine/outputFormatters/human/v1.ts +73 -0
  427. package/src/server/plugins/engine/outputFormatters/index.test.ts +17 -0
  428. package/src/server/plugins/engine/outputFormatters/index.ts +44 -0
  429. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +229 -0
  430. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +140 -0
  431. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +229 -0
  432. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +153 -0
  433. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1108 -0
  434. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +446 -0
  435. package/src/server/plugins/engine/pageControllers/PageController.test.ts +205 -0
  436. package/src/server/plugins/engine/pageControllers/PageController.ts +176 -0
  437. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1264 -0
  438. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +561 -0
  439. package/src/server/plugins/engine/pageControllers/README.md +28 -0
  440. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +264 -0
  441. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +458 -0
  442. package/src/server/plugins/engine/pageControllers/StartPageController.ts +18 -0
  443. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +50 -0
  444. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +261 -0
  445. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +28 -0
  446. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +19 -0
  447. package/src/server/plugins/engine/pageControllers/helpers.test.ts +198 -0
  448. package/src/server/plugins/engine/pageControllers/helpers.ts +101 -0
  449. package/src/server/plugins/engine/pageControllers/index.ts +10 -0
  450. package/src/server/plugins/engine/pageControllers/validationOptions.ts +89 -0
  451. package/src/server/plugins/engine/plugin.ts +673 -0
  452. package/src/server/plugins/engine/services/formSubmissionService.js +46 -0
  453. package/src/server/plugins/engine/services/formsService.js +46 -0
  454. package/src/server/plugins/engine/services/formsService.test.js +90 -0
  455. package/src/server/plugins/engine/services/index.js +3 -0
  456. package/src/server/plugins/engine/services/notifyService.test.ts +132 -0
  457. package/src/server/plugins/engine/services/notifyService.ts +64 -0
  458. package/src/server/plugins/engine/services/uploadService.js +60 -0
  459. package/src/server/plugins/engine/types.ts +315 -0
  460. package/src/server/plugins/engine/views/components/autocompletefield.html +5 -0
  461. package/src/server/plugins/engine/views/components/checkboxesfield.html +5 -0
  462. package/src/server/plugins/engine/views/components/datepartsfield.html +5 -0
  463. package/src/server/plugins/engine/views/components/details.html +6 -0
  464. package/src/server/plugins/engine/views/components/emailaddressfield.html +5 -0
  465. package/src/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
  466. package/src/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
  467. package/src/server/plugins/engine/views/components/fileuploadfield.html +24 -0
  468. package/src/server/plugins/engine/views/components/html.html +3 -0
  469. package/src/server/plugins/engine/views/components/insettext.html +7 -0
  470. package/src/server/plugins/engine/views/components/list.html +36 -0
  471. package/src/server/plugins/engine/views/components/monthyearfield.html +5 -0
  472. package/src/server/plugins/engine/views/components/multilinetextfield.html +10 -0
  473. package/src/server/plugins/engine/views/components/numberfield.html +5 -0
  474. package/src/server/plugins/engine/views/components/radiosfield.html +5 -0
  475. package/src/server/plugins/engine/views/components/selectfield.html +5 -0
  476. package/src/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
  477. package/src/server/plugins/engine/views/components/textfield.html +5 -0
  478. package/src/server/plugins/engine/views/components/ukaddressfield.html +25 -0
  479. package/src/server/plugins/engine/views/components/yesnofield.html +5 -0
  480. package/src/server/plugins/engine/views/file-upload.html +45 -0
  481. package/src/server/plugins/engine/views/index.html +39 -0
  482. package/src/server/plugins/engine/views/item-delete.html +56 -0
  483. package/src/server/plugins/engine/views/partials/components.html +6 -0
  484. package/src/server/plugins/engine/views/partials/conditional-components.html +3 -0
  485. package/src/server/plugins/engine/views/partials/debug.html +44 -0
  486. package/src/server/plugins/engine/views/partials/form.html +15 -0
  487. package/src/server/plugins/engine/views/partials/heading.html +16 -0
  488. package/src/server/plugins/engine/views/partials/preview-banner.html +32 -0
  489. package/src/server/plugins/engine/views/partials/preview-banner.test.js +122 -0
  490. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
  491. package/src/server/plugins/engine/views/repeat-list-summary.html +53 -0
  492. package/src/server/plugins/errorPages.ts +58 -0
  493. package/src/server/plugins/nunjucks/context.js +88 -0
  494. package/src/server/plugins/nunjucks/context.test.js +142 -0
  495. package/src/server/plugins/nunjucks/enviroment.test.js +201 -0
  496. package/src/server/plugins/nunjucks/environment.js +116 -0
  497. package/src/server/plugins/nunjucks/filters/answer.js +27 -0
  498. package/src/server/plugins/nunjucks/filters/answer.test.js +89 -0
  499. package/src/server/plugins/nunjucks/filters/evaluate.js +21 -0
  500. package/src/server/plugins/nunjucks/filters/field.js +28 -0
  501. package/src/server/plugins/nunjucks/filters/field.test.js +75 -0
  502. package/src/server/plugins/nunjucks/filters/highlight.js +11 -0
  503. package/src/server/plugins/nunjucks/filters/href.js +30 -0
  504. package/src/server/plugins/nunjucks/filters/href.test.js +80 -0
  505. package/src/server/plugins/nunjucks/filters/index.js +8 -0
  506. package/src/server/plugins/nunjucks/filters/inspect.js +15 -0
  507. package/src/server/plugins/nunjucks/filters/page.js +24 -0
  508. package/src/server/plugins/nunjucks/filters/page.test.js +65 -0
  509. package/src/server/plugins/nunjucks/index.js +3 -0
  510. package/src/server/plugins/nunjucks/plugin.js +40 -0
  511. package/src/server/plugins/nunjucks/render.js +42 -0
  512. package/src/server/plugins/nunjucks/types.js +40 -0
  513. package/src/server/plugins/pulse.ts +11 -0
  514. package/src/server/plugins/router.ts +201 -0
  515. package/src/server/plugins/session.ts +28 -0
  516. package/src/server/routes/health.js +13 -0
  517. package/src/server/routes/health.test.js +35 -0
  518. package/src/server/routes/index.test.ts +125 -0
  519. package/src/server/routes/index.ts +2 -0
  520. package/src/server/routes/public.ts +47 -0
  521. package/src/server/routes/types.ts +48 -0
  522. package/src/server/schemas/index.ts +34 -0
  523. package/src/server/secure-context.js +43 -0
  524. package/src/server/services/cacheService.test.ts +276 -0
  525. package/src/server/services/cacheService.ts +131 -0
  526. package/src/server/services/httpService.test.js +491 -0
  527. package/src/server/services/httpService.ts +50 -0
  528. package/src/server/services/index.ts +1 -0
  529. package/src/server/types.ts +54 -0
  530. package/src/server/utils/notify.test.ts +37 -0
  531. package/src/server/utils/notify.ts +50 -0
  532. package/src/server/utils/secure-context/get-trust-store-certs.js +11 -0
  533. package/src/server/utils/secure-context/get-trust-store-certs.test.js +19 -0
  534. package/src/server/utils/utils.js +24 -0
  535. package/src/server/utils/utils.test.js +54 -0
  536. package/src/server/views/404.html +16 -0
  537. package/src/server/views/500.html +19 -0
  538. package/src/server/views/components/debug/macro.njk +3 -0
  539. package/src/server/views/components/debug/template.njk +13 -0
  540. package/src/server/views/components/service-banner/macro.njk +3 -0
  541. package/src/server/views/components/service-banner/template.njk +20 -0
  542. package/src/server/views/components/service-banner/template.test.js +43 -0
  543. package/src/server/views/components/tag-env/macro.njk +3 -0
  544. package/src/server/views/components/tag-env/template.njk +30 -0
  545. package/src/server/views/components/tag-env/template.test.js +66 -0
  546. package/src/server/views/confirmation.html +19 -0
  547. package/src/server/views/help/accessibility-statement.html +58 -0
  548. package/src/server/views/help/cookie-preferences.html +57 -0
  549. package/src/server/views/help/cookies.html +71 -0
  550. package/src/server/views/help/get-support.html +37 -0
  551. package/src/server/views/help/privacy-notice.html +68 -0
  552. package/src/server/views/help/terms-and-conditions.html +83 -0
  553. package/src/server/views/layout.html +199 -0
  554. package/src/server/views/summary.html +50 -0
  555. package/src/typings/hapi/index.d.ts +95 -0
  556. package/src/typings/hapi-tracing/index.d.ts +6 -0
  557. package/src/typings/index.d.ts +3 -0
  558. package/src/typings/joi/index.d.ts +22 -0
  559. package/stylelint.config.js +10 -0
  560. package/test/client/javascripts/file-upload.test.js +1197 -0
  561. package/test/condition/checkboxes.test.js +112 -0
  562. package/test/condition/radios.test.js +112 -0
  563. package/test/condition/text.test.js +103 -0
  564. package/test/fixtures/assets-manifest.json +4 -0
  565. package/test/fixtures/form.js +86 -0
  566. package/test/fixtures/index.js +2 -0
  567. package/test/fixtures/list.js +92 -0
  568. package/test/form/cookies.test.js +338 -0
  569. package/test/form/csrf.test.js +87 -0
  570. package/test/form/definitions/basic.js +101 -0
  571. package/test/form/definitions/blank.js +10 -0
  572. package/test/form/definitions/checkboxes.json +88 -0
  573. package/test/form/definitions/components.json +452 -0
  574. package/test/form/definitions/conditional-reveal.js +140 -0
  575. package/test/form/definitions/conditions-basic.js +187 -0
  576. package/test/form/definitions/conditions-complex.js +338 -0
  577. package/test/form/definitions/conditions-dates.js +78 -0
  578. package/test/form/definitions/conditions-escaping.js +143 -0
  579. package/test/form/definitions/demo-cph-number.js +3099 -0
  580. package/test/form/definitions/feedback.json +45 -0
  581. package/test/form/definitions/fields-optional.js +402 -0
  582. package/test/form/definitions/fields-required.js +402 -0
  583. package/test/form/definitions/file-upload-basic.js +44 -0
  584. package/test/form/definitions/file-upload.js +66 -0
  585. package/test/form/definitions/minimal.js +39 -0
  586. package/test/form/definitions/phase-alpha.json +33 -0
  587. package/test/form/definitions/phase-default.json +26 -0
  588. package/test/form/definitions/radios.json +88 -0
  589. package/test/form/definitions/repeat-mixed.js +54 -0
  590. package/test/form/definitions/repeat.js +70 -0
  591. package/test/form/definitions/status.json +126 -0
  592. package/test/form/definitions/templates.js +183 -0
  593. package/test/form/definitions/test.json +581 -0
  594. package/test/form/definitions/text.json +75 -0
  595. package/test/form/definitions/titles.json +170 -0
  596. package/test/form/definitions.test.js +47 -0
  597. package/test/form/exit-page.test.js +210 -0
  598. package/test/form/feedback.test.js +68 -0
  599. package/test/form/fields-optional.test.js +237 -0
  600. package/test/form/fields-required.test.js +294 -0
  601. package/test/form/file-upload.test.js +313 -0
  602. package/test/form/govuk-notify.test.js +449 -0
  603. package/test/form/journey-basic.test.js +444 -0
  604. package/test/form/persist-files.test.js +227 -0
  605. package/test/form/phase-banner.test.js +71 -0
  606. package/test/form/repeat.test.js +628 -0
  607. package/test/form/summary-submission-email.test.js +95 -0
  608. package/test/form/template.test.js +288 -0
  609. package/test/form/titles.test.js +204 -0
  610. package/test/helpers/component-helpers.js +74 -0
  611. package/test/utils/get-cookie.js +42 -0
  612. package/test/utils/get-form-definitions.js +18 -0
  613. package/tmp.pdf +1 -0
  614. package/tsconfig.json +28 -0
  615. package/webpack.config.js +208 -0
@@ -0,0 +1,401 @@
1
+ import { ComponentType } from '@defra/forms-model';
2
+ import Boom from '@hapi/boom';
3
+ import { wait } from '@hapi/hoek';
4
+ import { tempItemSchema } from "../components/FileUploadField.js";
5
+ import { getError, getExponentialBackoffDelay } from "../helpers.js";
6
+ import { QuestionPageController } from "./QuestionPageController.js";
7
+ import { getProxyUrlForLocalDevelopment } from "./helpers.js";
8
+ import { getUploadStatus, initiateUpload } from "../services/uploadService.js";
9
+ import { FileStatus, UploadStatus } from "../types.js";
10
+ const MAX_UPLOADS = 25;
11
+ const CDP_UPLOAD_TIMEOUT_MS = 60000; // 1 minute
12
+
13
+ export function prepareStatus(status) {
14
+ const file = status.form.file;
15
+ const isPending = file.fileStatus === FileStatus.pending;
16
+ if (!file.errorMessage && isPending) {
17
+ file.errorMessage = 'The selected file has not fully uploaded';
18
+ }
19
+ return status;
20
+ }
21
+ function prepareFileState(fileState) {
22
+ prepareStatus(fileState.status);
23
+ return fileState;
24
+ }
25
+ export class FileUploadPageController extends QuestionPageController {
26
+ fileUpload;
27
+ fileDeleteViewName = 'item-delete';
28
+ constructor(model, pageDef) {
29
+ super(model, pageDef);
30
+ const {
31
+ collection
32
+ } = this;
33
+
34
+ // Get the file upload fields from the collection
35
+ const fileUploads = collection.fields.filter(field => field.type === ComponentType.FileUploadField);
36
+ const fileUpload = fileUploads.at(0);
37
+
38
+ // Assert we have exactly 1 file upload component
39
+ if (!fileUpload || fileUploads.length > 1) {
40
+ throw Boom.badImplementation(`Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`);
41
+ }
42
+
43
+ // Assert the file upload component is the first form component
44
+ if (collection.fields.indexOf(fileUpload) !== 0) {
45
+ throw Boom.badImplementation(`Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`);
46
+ }
47
+
48
+ // Assign the file upload component to the controller
49
+ this.fileUpload = fileUpload;
50
+ this.viewName = 'file-upload';
51
+ }
52
+ getFormDataFromState(request, state) {
53
+ const {
54
+ fileUpload
55
+ } = this;
56
+ const payload = super.getFormDataFromState(request, state);
57
+ const files = this.getFilesFromState(state);
58
+
59
+ // Append the files to the payload
60
+ payload[fileUpload.name] = files.length ? files : undefined;
61
+ return payload;
62
+ }
63
+ async getState(request) {
64
+ const {
65
+ fileUpload
66
+ } = this;
67
+
68
+ // Get the actual state
69
+ const state = await super.getState(request);
70
+ const files = this.getFilesFromState(state);
71
+
72
+ // Overwrite the files with those in the upload state
73
+ state[fileUpload.name] = files;
74
+ return this.refreshUpload(request, state);
75
+ }
76
+
77
+ /**
78
+ * Get the uploaded files from state.
79
+ */
80
+ getFilesFromState(state) {
81
+ const {
82
+ path
83
+ } = this;
84
+ const uploadState = state.upload?.[path];
85
+ return uploadState?.files ?? [];
86
+ }
87
+
88
+ /**
89
+ * Get the initiated upload from state.
90
+ */
91
+ getUploadFromState(state) {
92
+ const {
93
+ path
94
+ } = this;
95
+ const uploadState = state.upload?.[path];
96
+ return uploadState?.upload;
97
+ }
98
+ makeGetItemDeleteRouteHandler() {
99
+ return (request, context, h) => {
100
+ const {
101
+ viewModel
102
+ } = this;
103
+ const {
104
+ params
105
+ } = request;
106
+ const {
107
+ state
108
+ } = context;
109
+ const files = this.getFilesFromState(state);
110
+ const fileToRemove = files.find(({
111
+ uploadId
112
+ }) => uploadId === params.itemId);
113
+ if (!fileToRemove) {
114
+ throw Boom.notFound('File to delete not found');
115
+ }
116
+ const {
117
+ filename
118
+ } = fileToRemove.status.form.file;
119
+ return h.view(this.fileDeleteViewName, {
120
+ ...viewModel,
121
+ context,
122
+ backLink: this.getBackLink(request, context),
123
+ pageTitle: `Are you sure you want to remove this file?`,
124
+ itemTitle: filename,
125
+ confirmation: {
126
+ text: 'You cannot recover removed files.'
127
+ },
128
+ buttonConfirm: {
129
+ text: 'Remove file'
130
+ },
131
+ buttonCancel: {
132
+ text: 'Cancel'
133
+ }
134
+ });
135
+ };
136
+ }
137
+ makePostItemDeleteRouteHandler() {
138
+ return async (request, context, h) => {
139
+ const {
140
+ path
141
+ } = this;
142
+ const {
143
+ state
144
+ } = context;
145
+ const {
146
+ confirm
147
+ } = this.getFormParams(request);
148
+
149
+ // Check for any removed files in the POST payload
150
+ if (confirm) {
151
+ await this.checkRemovedFiles(request, state);
152
+ return this.proceed(request, h, path);
153
+ }
154
+ return this.proceed(request, h);
155
+ };
156
+ }
157
+ getErrors(details) {
158
+ const {
159
+ fileUpload
160
+ } = this;
161
+ if (details) {
162
+ const errors = [];
163
+ details.forEach(error => {
164
+ const isUploadError = error.path[0] === fileUpload.name;
165
+ const isUploadRootError = isUploadError && error.path.length === 1;
166
+ if (!isUploadError || isUploadRootError) {
167
+ // The error is for the root of the upload or another
168
+ // field on the page so defer to the getError helper
169
+ errors.push(getError(error));
170
+ } else {
171
+ const {
172
+ context,
173
+ path,
174
+ type
175
+ } = error;
176
+ if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {
177
+ const value = context?.value;
178
+ if (value) {
179
+ const name = fileUpload.name;
180
+ const text = typeof value === 'string' ? value : 'Unknown error';
181
+ const href = `#${name}`;
182
+ errors.push({
183
+ path,
184
+ href,
185
+ name,
186
+ text
187
+ });
188
+ }
189
+ }
190
+ }
191
+ });
192
+ return errors;
193
+ }
194
+ }
195
+ getViewModel(request, context) {
196
+ const {
197
+ fileUpload
198
+ } = this;
199
+ const {
200
+ state
201
+ } = context;
202
+ const upload = this.getUploadFromState(state);
203
+ const viewModel = super.getViewModel(request, context);
204
+ const {
205
+ components
206
+ } = viewModel;
207
+
208
+ // Featured form component
209
+ const [formComponent] = components.filter(({
210
+ model
211
+ }) => model.id === fileUpload.name);
212
+ const index = components.indexOf(formComponent);
213
+ const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl);
214
+ return {
215
+ ...viewModel,
216
+ formAction: upload?.uploadUrl,
217
+ uploadId: upload?.uploadId,
218
+ formComponent,
219
+ // Split out components before/after
220
+ componentsBefore: components.slice(0, index),
221
+ components: components.slice(index),
222
+ proxyUrl
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Refreshes the CDP upload and files in the
228
+ * state and checks for any removed files.
229
+ *
230
+ * If an upload exists and hasn't been consumed
231
+ * it gets re-used, otherwise we initiate a new one.
232
+ * @param request - the hapi request
233
+ * @param state - the form state
234
+ */
235
+ async refreshUpload(request, state) {
236
+ state = await this.checkUploadStatus(request, state);
237
+ return state;
238
+ }
239
+
240
+ /**
241
+ * If an upload exists and hasn't been consumed
242
+ * it gets re-used, otherwise a new one is initiated.
243
+ * @param request - the hapi request
244
+ * @param state - the form state
245
+ * @param depth - the number of retries so far
246
+ */
247
+ async checkUploadStatus(request, state, depth = 1) {
248
+ const upload = this.getUploadFromState(state);
249
+ const files = this.getFilesFromState(state);
250
+
251
+ // If no upload exists, initiate a new one.
252
+ if (!upload?.uploadId) {
253
+ return this.initiateAndStoreNewUpload(request, state);
254
+ }
255
+ const uploadId = upload.uploadId;
256
+ const statusResponse = await getUploadStatus(uploadId);
257
+ if (!statusResponse) {
258
+ throw Boom.badRequest(`Unexpected empty response from getUploadStatus for ${uploadId}`);
259
+ }
260
+
261
+ // Re-use the upload if it is still in the "initiated" state.
262
+ if (statusResponse.uploadStatus === UploadStatus.initiated) {
263
+ return state;
264
+ }
265
+ if (statusResponse.uploadStatus === UploadStatus.pending) {
266
+ // Using exponential backoff delays:
267
+ // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)
268
+ // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.
269
+ if (depth >= 5) {
270
+ request.logger.error(`Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`);
271
+ await this.initiateAndStoreNewUpload(request, state);
272
+ throw Boom.gatewayTimeout(`Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`);
273
+ }
274
+ const delay = getExponentialBackoffDelay(depth);
275
+ request.logger.info(`Waiting ${delay / 1000} seconds for ${uploadId} to complete (depth: ${depth})`);
276
+ await wait(delay);
277
+ return this.checkUploadStatus(request, state, depth + 1);
278
+ }
279
+
280
+ // Only add to files state if the file validates.
281
+ // This secures against html tampering of the file input
282
+ // by adding a 'multiple' attribute or it being
283
+ // changed to a simple text field or similar.
284
+ const validationResult = tempItemSchema.validate({
285
+ uploadId,
286
+ status: statusResponse
287
+ }, {
288
+ stripUnknown: true
289
+ });
290
+ const error = validationResult.error;
291
+ const fileState = validationResult.value;
292
+ if (error) {
293
+ return this.initiateAndStoreNewUpload(request, state);
294
+ }
295
+ const file = fileState.status.form.file;
296
+ if (file.fileStatus === FileStatus.complete) {
297
+ files.unshift(prepareFileState(fileState));
298
+ await this.mergeState(request, state, {
299
+ upload: {
300
+ [this.path]: {
301
+ files,
302
+ upload
303
+ }
304
+ }
305
+ });
306
+ } else {
307
+ // Flash the error message.
308
+ const {
309
+ fileUpload
310
+ } = this;
311
+ const {
312
+ cacheService
313
+ } = request.services([]);
314
+ const name = fileUpload.name;
315
+ const text = file.errorMessage ?? 'Unknown error';
316
+ const errors = [{
317
+ path: [name],
318
+ href: `#${name}`,
319
+ name,
320
+ text
321
+ }];
322
+ cacheService.setFlash(request, {
323
+ errors
324
+ });
325
+ }
326
+ return this.initiateAndStoreNewUpload(request, state);
327
+ }
328
+
329
+ /**
330
+ * Checks the payload for a file getting removed
331
+ * and removes it from the upload files if found
332
+ * @param request - the hapi request
333
+ * @param state - the form state
334
+ * @returns updated state if any files have been removed
335
+ */
336
+ async checkRemovedFiles(request, state) {
337
+ const {
338
+ path
339
+ } = this;
340
+ const {
341
+ params
342
+ } = request;
343
+ const upload = this.getUploadFromState(state);
344
+ const files = this.getFilesFromState(state);
345
+ const filesUpdated = files.filter(({
346
+ uploadId
347
+ }) => uploadId !== params.itemId);
348
+ if (filesUpdated.length === files.length) {
349
+ return;
350
+ }
351
+ await this.mergeState(request, state, {
352
+ upload: {
353
+ [path]: {
354
+ files: filesUpdated,
355
+ upload
356
+ }
357
+ }
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Initiates a CDP file upload and stores in the upload state
363
+ * @param request - the hapi request
364
+ * @param state - the form state
365
+ */
366
+ async initiateAndStoreNewUpload(request, state) {
367
+ const {
368
+ fileUpload,
369
+ href,
370
+ path
371
+ } = this;
372
+ const {
373
+ options,
374
+ schema
375
+ } = fileUpload;
376
+ const files = this.getFilesFromState(state);
377
+
378
+ // Reset the upload in state
379
+ let upload;
380
+
381
+ // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS
382
+ const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS);
383
+ if (files.length < max) {
384
+ const outputEmail = this.model.def.outputEmail ?? 'defraforms@defra.gov.uk';
385
+ const newUpload = await initiateUpload(href, outputEmail, options.accept);
386
+ if (newUpload === undefined) {
387
+ throw Boom.badRequest('Unexpected empty response from initiateUpload');
388
+ }
389
+ upload = newUpload;
390
+ }
391
+ return this.mergeState(request, state, {
392
+ upload: {
393
+ [path]: {
394
+ files,
395
+ upload
396
+ }
397
+ }
398
+ });
399
+ }
400
+ }
401
+ //# sourceMappingURL=FileUploadPageController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","services","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit } from '@hapi/hapi'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type FeaturedFormPageViewModel,\n type FileState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ uploadId }) => uploadId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n request.logger.error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `Waiting ${delay / 1000} seconds for ${uploadId} to complete (depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const { cacheService } = request.services([])\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,133 @@
1
+ import { ControllerPath } from '@defra/forms-model';
2
+ import Boom from '@hapi/boom';
3
+ import { encodeUrl, getStartPath, normalisePath } from "../helpers.js";
4
+ export class PageController {
5
+ /**
6
+ * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.
7
+ */
8
+ def;
9
+ name;
10
+ model;
11
+ pageDef;
12
+ title;
13
+ section;
14
+ condition;
15
+ events;
16
+ collection;
17
+ viewName = 'index';
18
+ constructor(model, pageDef) {
19
+ const {
20
+ def
21
+ } = model;
22
+ this.def = def;
23
+ this.name = def.name;
24
+ this.model = model;
25
+ this.pageDef = pageDef;
26
+ this.title = pageDef.title;
27
+ this.events = pageDef.events;
28
+
29
+ // Resolve section
30
+ this.section = model.sections.find(section => section.name === pageDef.section);
31
+
32
+ // Resolve condition
33
+ if (pageDef.condition) {
34
+ this.condition = model.conditions[pageDef.condition];
35
+ }
36
+
37
+ // Override view name
38
+ if (pageDef.view) {
39
+ this.viewName = pageDef.view;
40
+ }
41
+ }
42
+ get path() {
43
+ return this.pageDef.path;
44
+ }
45
+ get href() {
46
+ const {
47
+ path
48
+ } = this;
49
+ return this.getHref(`/${normalisePath(path)}`);
50
+ }
51
+ get keys() {
52
+ return this.collection?.keys ?? [];
53
+ }
54
+
55
+ /**
56
+ * {@link https://hapi.dev/api/?v=20.1.2#route-options}
57
+ */
58
+ get getRouteOptions() {
59
+ return {};
60
+ }
61
+
62
+ /**
63
+ * {@link https://hapi.dev/api/?v=20.1.2#route-options}
64
+ */
65
+ get postRouteOptions() {
66
+ return {};
67
+ }
68
+ get viewModel() {
69
+ const {
70
+ name,
71
+ section,
72
+ title
73
+ } = this;
74
+ const showTitle = true;
75
+ const pageTitle = title;
76
+ const sectionTitle = section?.hideTitle !== true ? section?.title : '';
77
+ return {
78
+ name,
79
+ page: this,
80
+ pageTitle,
81
+ sectionTitle,
82
+ showTitle,
83
+ isStartPage: false,
84
+ serviceUrl: this.getHref('/'),
85
+ feedbackLink: this.feedbackLink,
86
+ phaseTag: this.phaseTag
87
+ };
88
+ }
89
+ get feedbackLink() {
90
+ const {
91
+ def
92
+ } = this;
93
+
94
+ // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown
95
+ const feedbackLink = def.feedback?.emailAddress ? `mailto:${def.feedback.emailAddress}` : def.feedback?.url;
96
+ return encodeUrl(feedbackLink);
97
+ }
98
+ get phaseTag() {
99
+ const {
100
+ def
101
+ } = this;
102
+ return def.phaseBanner?.phase;
103
+ }
104
+ getHref(path) {
105
+ const {
106
+ model
107
+ } = this;
108
+ return path === '/' ? `/${model.basePath}` // Strip trailing slash
109
+ : `/${model.basePath}${path}`;
110
+ }
111
+ getStartPath() {
112
+ return getStartPath(this.model);
113
+ }
114
+ getSummaryPath() {
115
+ return ControllerPath.Summary.valueOf();
116
+ }
117
+ getStatusPath() {
118
+ return ControllerPath.Status.valueOf();
119
+ }
120
+ makeGetRouteHandler() {
121
+ return (request, context, h) => {
122
+ const {
123
+ viewModel,
124
+ viewName
125
+ } = this;
126
+ return h.view(viewName, viewModel);
127
+ };
128
+ }
129
+ makePostRouteHandler() {
130
+ throw Boom.badRequest('Unsupported POST route handler for this page');
131
+ }
132
+ }
133
+ //# sourceMappingURL=PageController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string) {\n const { model } = this\n\n return path === '/'\n ? `/${model.basePath}` // Strip trailing slash\n : `/${model.basePath}${path}`\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n}\n"],"mappings":"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","ignoreList":[]}