@defra/forms-engine-plugin 2.1.10 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.server/server/index.js +2 -1
  2. package/.server/server/index.js.map +1 -1
  3. package/.server/server/plugins/engine/README.md +2 -2
  4. package/.server/server/plugins/engine/configureEnginePlugin.d.ts +2 -1
  5. package/.server/server/plugins/engine/configureEnginePlugin.js +4 -4
  6. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  7. package/.server/server/plugins/engine/helpers.d.ts +7 -11
  8. package/.server/server/plugins/engine/helpers.js +2 -2
  9. package/.server/server/plugins/engine/helpers.js.map +1 -1
  10. package/.server/server/plugins/engine/models/FormModel.js +1 -1
  11. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  12. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
  13. package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
  14. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  15. package/.server/server/plugins/engine/options.js +3 -6
  16. package/.server/server/plugins/engine/options.js.map +1 -1
  17. package/.server/server/plugins/engine/options.test.js +2 -8
  18. package/.server/server/plugins/engine/options.test.js.map +1 -1
  19. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +5 -6
  20. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  21. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +6 -6
  22. package/.server/server/plugins/engine/pageControllers/PageController.js +4 -4
  23. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  24. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +13 -13
  25. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +12 -20
  26. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  27. package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +7 -8
  28. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  29. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
  30. package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -1
  31. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
  32. package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +3 -4
  33. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -1
  34. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  35. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +5 -4
  36. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +12 -1
  37. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  38. package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +4 -4
  39. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -1
  40. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
  41. package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +1 -1
  42. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +2 -6
  43. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -1
  44. package/.server/server/plugins/engine/plugin.js +7 -12
  45. package/.server/server/plugins/engine/plugin.js.map +1 -1
  46. package/.server/server/plugins/engine/routes/index.d.ts +5 -5
  47. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  48. package/.server/server/plugins/engine/routes/questions.d.ts +4 -4
  49. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  50. package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -1
  51. package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -1
  52. package/.server/server/plugins/engine/types/index.d.ts +2 -2
  53. package/.server/server/plugins/engine/types/index.js.map +1 -1
  54. package/.server/server/plugins/engine/types.d.ts +10 -11
  55. package/.server/server/plugins/engine/types.js.map +1 -1
  56. package/.server/server/plugins/engine/views/partials/form.html +3 -3
  57. package/.server/server/plugins/engine/views/summary.html +21 -5
  58. package/.server/server/plugins/nunjucks/context.d.ts +5 -6
  59. package/.server/server/plugins/nunjucks/context.js +3 -3
  60. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  61. package/.server/server/routes/types.d.ts +3 -2
  62. package/.server/server/routes/types.js +1 -1
  63. package/.server/server/routes/types.js.map +1 -1
  64. package/.server/server/schemas/index.js +1 -1
  65. package/.server/server/schemas/index.js.map +1 -1
  66. package/.server/server/services/cacheService.d.ts +11 -19
  67. package/.server/server/services/cacheService.js +9 -30
  68. package/.server/server/services/cacheService.js.map +1 -1
  69. package/.server/server/types.d.ts +4 -1
  70. package/.server/server/types.js.map +1 -1
  71. package/.server/typings/hapi/index.d.js.map +1 -1
  72. package/package.json +3 -1
  73. package/src/server/index.test.ts +0 -39
  74. package/src/server/index.ts +4 -1
  75. package/src/server/plugins/engine/README.md +2 -2
  76. package/src/server/plugins/engine/components/helpers/helpers.test.ts +1 -1
  77. package/src/server/plugins/engine/configureEnginePlugin.ts +15 -11
  78. package/src/server/plugins/engine/helpers.test.ts +3 -2
  79. package/src/server/plugins/engine/helpers.ts +6 -6
  80. package/src/server/plugins/engine/models/FormModel.test.ts +2 -2
  81. package/src/server/plugins/engine/models/FormModel.ts +1 -2
  82. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -7
  83. package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
  84. package/src/server/plugins/engine/options.js +6 -6
  85. package/src/server/plugins/engine/options.test.js +2 -6
  86. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +8 -10
  87. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +9 -8
  88. package/src/server/plugins/engine/pageControllers/PageController.test.ts +11 -8
  89. package/src/server/plugins/engine/pageControllers/PageController.ts +9 -15
  90. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +35 -102
  91. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +24 -36
  92. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +4 -6
  93. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -11
  94. package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +4 -4
  95. package/src/server/plugins/engine/pageControllers/StartPageController.ts +1 -1
  96. package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +4 -4
  97. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +6 -4
  98. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -5
  99. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +4 -4
  100. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +7 -4
  101. package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +5 -6
  102. package/src/server/plugins/engine/plugin.ts +7 -13
  103. package/src/server/plugins/engine/routes/index.ts +6 -11
  104. package/src/server/plugins/engine/routes/questions.test.ts +29 -53
  105. package/src/server/plugins/engine/routes/questions.ts +6 -8
  106. package/src/server/plugins/engine/routes/repeaters/item-delete.ts +5 -14
  107. package/src/server/plugins/engine/routes/repeaters/summary.ts +5 -14
  108. package/src/server/plugins/engine/types/index.ts +4 -1
  109. package/src/server/plugins/engine/types.ts +19 -13
  110. package/src/server/plugins/engine/views/partials/form.html +3 -3
  111. package/src/server/plugins/engine/views/summary.html +21 -5
  112. package/src/server/plugins/nunjucks/context.js +3 -3
  113. package/src/server/routes/types.ts +7 -2
  114. package/src/server/schemas/index.ts +1 -1
  115. package/src/server/services/cacheService.test.ts +1 -117
  116. package/src/server/services/cacheService.ts +22 -73
  117. package/src/server/types.ts +4 -1
  118. package/src/typings/hapi/index.d.ts +6 -7
  119. package/.server/server/plugins/engine/routes/exit.d.ts +0 -46
  120. package/.server/server/plugins/engine/routes/exit.js +0 -36
  121. package/.server/server/plugins/engine/routes/exit.js.map +0 -1
  122. package/src/server/plugins/engine/routes/exit.ts +0 -47
@@ -1,11 +1,12 @@
1
1
  import Joi from 'joi';
2
2
  import { createLogger } from "../../common/helpers/logging/logger.js";
3
+ import { CacheService } from "../../services/index.js";
3
4
  const logger = createLogger();
4
5
  const pluginRegistrationOptionsSchema = Joi.object({
5
6
  model: Joi.object().optional(),
6
7
  services: Joi.object().optional(),
7
8
  controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
8
- cacheName: Joi.string().optional(),
9
+ cache: Joi.alternatives().try(Joi.object().instance(CacheService), Joi.string()),
9
10
  globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
10
11
  filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
11
12
  pluginPath: Joi.string().optional(),
@@ -17,11 +18,7 @@ const pluginRegistrationOptionsSchema = Joi.object({
17
18
  preparePageEventRequestOptions: Joi.function().optional(),
18
19
  onRequest: Joi.function().optional(),
19
20
  baseUrl: Joi.string().uri().required(),
20
- saveAndReturn: Joi.object({
21
- keyGenerator: Joi.function(),
22
- sessionHydrator: Joi.function(),
23
- sessionPersister: Joi.function()
24
- }).optional()
21
+ saveAndExit: Joi.function().optional()
25
22
  });
26
23
 
27
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","names":["Joi","createLogger","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","globals","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","saveAndReturn","keyGenerator","sessionHydrator","sessionPersister","validatePluginOptions","options","result","validate","abortEarly","error","message","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\n\nconst logger = createLogger()\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cacheName: Joi.string().optional(),\n globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional(),\n baseUrl: Joi.string().uri().required(),\n saveAndReturn: Joi.object({\n keyGenerator: Joi.function(),\n sessionHydrator: Joi.function(),\n sessionPersister: Joi.function()\n }).optional()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n logger.error(\n `Missing required properties in plugin options: ${result.error.message}`\n )\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB,MAAMC,MAAM,GAAGD,YAAY,CAAC,CAAC;AAE7B,MAAME,+BAA+B,GAAGH,GAAG,CAACI,MAAM,CAAC;EACjDC,KAAK,EAAEL,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEP,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAER,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEZ,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEb,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,OAAO,EAAEd,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjES,UAAU,EAAEf,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCU,QAAQ,EAAEhB,GAAG,CAACI,MAAM,CAAC;IACnBa,cAAc,EAAEjB,GAAG,CAACU,MAAM,CAAC,CAAC,CAACQ,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEnB,GAAG,CAACoB,KAAK,CAAC,CAAC,CAACC,KAAK,CAACrB,GAAG,CAACU,MAAM,CAAC,CAAC,CAAC,CAACQ,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAEtB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAExB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACzDmB,SAAS,EAAEzB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACpCoB,OAAO,EAAE1B,GAAG,CAACU,MAAM,CAAC,CAAC,CAACiB,GAAG,CAAC,CAAC,CAACT,QAAQ,CAAC,CAAC;EACtCU,aAAa,EAAE5B,GAAG,CAACI,MAAM,CAAC;IACxByB,YAAY,EAAE7B,GAAG,CAACuB,QAAQ,CAAC,CAAC;IAC5BO,eAAe,EAAE9B,GAAG,CAACuB,QAAQ,CAAC,CAAC;IAC/BQ,gBAAgB,EAAE/B,GAAG,CAACuB,QAAQ,CAAC;EACjC,CAAC,CAAC,CAACjB,QAAQ,CAAC;AACd,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS0B,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAG/B,+BAA+B,CAACgC,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChBnC,MAAM,CAACmC,KAAK,CACV,kDAAkDH,MAAM,CAACG,KAAK,CAACC,OAAO,EACxE,CAAC;IACD,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEL,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACM,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"options.js","names":["Joi","createLogger","CacheService","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cache","alternatives","try","instance","globals","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","saveAndExit","validatePluginOptions","options","result","validate","abortEarly","error","message","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nconst logger = createLogger()\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cache: Joi.alternatives().try(\n Joi.object().instance(CacheService),\n Joi.string()\n ),\n globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional(),\n baseUrl: Joi.string().uri().required(),\n saveAndExit: Joi.function().optional()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n logger.error(\n `Missing required properties in plugin options: ${result.error.message}`\n )\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AACrB,SAASC,YAAY;AAErB,MAAMC,MAAM,GAAGF,YAAY,CAAC,CAAC;AAE7B,MAAMG,+BAA+B,GAAGJ,GAAG,CAACK,MAAM,CAAC;EACjDC,KAAK,EAAEN,GAAG,CAACK,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAER,GAAG,CAACK,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAET,GAAG,CAACK,MAAM,CAAC,CAAC,CAACK,OAAO,CAACV,GAAG,CAACW,MAAM,CAAC,CAAC,EAAEX,GAAG,CAACY,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,KAAK,EAAEb,GAAG,CAACc,YAAY,CAAC,CAAC,CAACC,GAAG,CAC3Bf,GAAG,CAACK,MAAM,CAAC,CAAC,CAACW,QAAQ,CAACd,YAAY,CAAC,EACnCF,GAAG,CAACW,MAAM,CAAC,CACb,CAAC;EACDM,OAAO,EAAEjB,GAAG,CAACK,MAAM,CAAC,CAAC,CAACK,OAAO,CAACV,GAAG,CAACW,MAAM,CAAC,CAAC,EAAEX,GAAG,CAACY,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEW,OAAO,EAAElB,GAAG,CAACK,MAAM,CAAC,CAAC,CAACK,OAAO,CAACV,GAAG,CAACW,MAAM,CAAC,CAAC,EAAEX,GAAG,CAACY,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEY,UAAU,EAAEnB,GAAG,CAACW,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCa,QAAQ,EAAEpB,GAAG,CAACK,MAAM,CAAC;IACnBgB,cAAc,EAAErB,GAAG,CAACW,MAAM,CAAC,CAAC,CAACW,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEvB,GAAG,CAACwB,KAAK,CAAC,CAAC,CAACC,KAAK,CAACzB,GAAG,CAACW,MAAM,CAAC,CAAC,CAAC,CAACW,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAE1B,GAAG,CAAC2B,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAE5B,GAAG,CAAC2B,QAAQ,CAAC,CAAC,CAACpB,QAAQ,CAAC,CAAC;EACzDsB,SAAS,EAAE7B,GAAG,CAAC2B,QAAQ,CAAC,CAAC,CAACpB,QAAQ,CAAC,CAAC;EACpCuB,OAAO,EAAE9B,GAAG,CAACW,MAAM,CAAC,CAAC,CAACoB,GAAG,CAAC,CAAC,CAACT,QAAQ,CAAC,CAAC;EACtCU,WAAW,EAAEhC,GAAG,CAAC2B,QAAQ,CAAC,CAAC,CAACpB,QAAQ,CAAC;AACvC,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS0B,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAG/B,+BAA+B,CAACgC,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChBnC,MAAM,CAACmC,KAAK,CACV,kDAAkDH,MAAM,CAACG,KAAK,CAACC,OAAO,EACxE,CAAC;IACD,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEL,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACM,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
@@ -18,7 +18,7 @@ describe('validatePluginOptions', () => {
18
18
  };
19
19
  expect(validatePluginOptions(validOptions)).toEqual(validOptions);
20
20
  });
21
- it('accepts optional properties keyGenerator, sessionHydrator, and sessionPersister', () => {
21
+ it('accepts optional property saveAndExit', () => {
22
22
  /**
23
23
  * @type {PluginOptions}
24
24
  */
@@ -33,13 +33,7 @@ describe('validatePluginOptions', () => {
33
33
  };
34
34
  },
35
35
  baseUrl: 'http://localhost:3009',
36
- saveAndReturn: {
37
- keyGenerator: () => 'test-key',
38
- sessionHydrator: () => Promise.resolve({
39
- someState: 'value'
40
- }),
41
- sessionPersister: () => Promise.resolve(undefined)
42
- }
36
+ saveAndExit: (request, h) => h.redirect('/save-and-exit')
43
37
  };
44
38
  expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(validOptionsWithOptionals);
45
39
  });
@@ -1 +1 @@
1
- {"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","baseUrl","expect","toEqual","validOptionsWithOptionals","saveAndReturn","keyGenerator","sessionHydrator","Promise","resolve","someState","sessionPersister","undefined","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n /**\n * @type {PluginOptions}\n */\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009'\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n it('accepts optional properties keyGenerator, sessionHydrator, and sessionPersister', () => {\n /**\n * @type {PluginOptions}\n */\n const validOptionsWithOptionals = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver']\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009',\n saveAndReturn: {\n keyGenerator: () => 'test-key',\n sessionHydrator: () => Promise.resolve({ someState: 'value' }),\n sessionPersister: () => Promise.resolve(undefined)\n }\n }\n\n expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(\n validOptionsWithOptionals\n )\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n const invalidOptions =\n /** @type {PluginOptions} testing without viewContext */ ({\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n })\n\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD;AACJ;AACA;IACI,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE;IACX,CAAC;IAEDC,MAAM,CAACV,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACQ,OAAO,CAACR,YAAY,CAAC;EACnE,CAAC,CAAC;EAEFD,EAAE,CAAC,iFAAiF,EAAE,MAAM;IAC1F;AACJ;AACA;IACI,MAAMU,yBAAyB,GAAG;MAChCR,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB;MAChC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE,uBAAuB;MAChCI,aAAa,EAAE;QACbC,YAAY,EAAEA,CAAA,KAAM,UAAU;QAC9BC,eAAe,EAAEA,CAAA,KAAMC,OAAO,CAACC,OAAO,CAAC;UAAEC,SAAS,EAAE;QAAQ,CAAC,CAAC;QAC9DC,gBAAgB,EAAEA,CAAA,KAAMH,OAAO,CAACC,OAAO,CAACG,SAAS;MACnD;IACF,CAAC;IAEDV,MAAM,CAACV,qBAAqB,CAACY,yBAAyB,CAAC,CAAC,CAACD,OAAO,CAC9DC,yBACF,CAAC;EACH,CAAC,CAAC;;EAEF;AACF;AACA;EACEV,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD,MAAMmB,cAAc,GAClB,wDAA0D;MACxDjB,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAE;IAEJI,MAAM,CAAC,MAAMV,qBAAqB,CAACqB,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","baseUrl","expect","toEqual","validOptionsWithOptionals","saveAndExit","request","h","redirect","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n /**\n * @type {PluginOptions}\n */\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009'\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n it('accepts optional property saveAndExit', () => {\n /**\n * @type {PluginOptions}\n */\n const validOptionsWithOptionals = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver']\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009',\n saveAndExit: (request, h) => h.redirect('/save-and-exit')\n }\n\n expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(\n validOptionsWithOptionals\n )\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n const invalidOptions =\n /** @type {PluginOptions} testing without viewContext */ ({\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n })\n\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD;AACJ;AACA;IACI,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE;IACX,CAAC;IAEDC,MAAM,CAACV,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACQ,OAAO,CAACR,YAAY,CAAC;EACnE,CAAC,CAAC;EAEFD,EAAE,CAAC,uCAAuC,EAAE,MAAM;IAChD;AACJ;AACA;IACI,MAAMU,yBAAyB,GAAG;MAChCR,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB;MAChC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE,uBAAuB;MAChCI,WAAW,EAAEA,CAACC,OAAO,EAAEC,CAAC,KAAKA,CAAC,CAACC,QAAQ,CAAC,gBAAgB;IAC1D,CAAC;IAEDN,MAAM,CAACV,qBAAqB,CAACY,yBAAyB,CAAC,CAAC,CAACD,OAAO,CAC9DC,yBACF,CAAC;EACH,CAAC,CAAC;;EAEF;AACF;AACA;EACEV,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD,MAAMe,cAAc,GAClB,wDAA0D;MACxDb,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAE;IAEJI,MAAM,CAAC,MAAMV,qBAAqB,CAACiB,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA","ignoreList":[]}
@@ -1,11 +1,10 @@
1
1
  import { type PageFileUpload } from '@defra/forms-model';
2
- import { type ResponseToolkit } from '@hapi/hapi';
3
2
  import { type ValidationErrorItem } from 'joi';
4
3
  import { type FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js';
5
4
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
6
5
  import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js';
7
- import { type FeaturedFormPageViewModel, type FormContext, type FormContextRequest, type FormSubmissionError, type FormSubmissionState, type UploadInitiateResponse, type UploadStatusFileResponse } from '~/src/server/plugins/engine/types.js';
8
- import { type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
6
+ import { type AnyFormRequest, type FeaturedFormPageViewModel, type FormContext, type FormContextRequest, type FormSubmissionError, type FormSubmissionState, type UploadInitiateResponse, type UploadStatusFileResponse } from '~/src/server/plugins/engine/types.js';
7
+ import { type FormRequest, type FormRequestPayload, type FormResponseToolkit } from '~/src/server/routes/types.js';
9
8
  export declare function prepareStatus(status: UploadStatusFileResponse): UploadStatusFileResponse;
10
9
  export declare class FileUploadPageController extends QuestionPageController {
11
10
  pageDef: PageFileUpload;
@@ -13,7 +12,7 @@ export declare class FileUploadPageController extends QuestionPageController {
13
12
  fileDeleteViewName: string;
14
13
  constructor(model: FormModel, pageDef: PageFileUpload);
15
14
  getFormDataFromState(request: FormContextRequest | undefined, state: FormSubmissionState): import("~/src/server/plugins/engine/types.js").FormPayload;
16
- getState(request: FormRequest | FormRequestPayload): Promise<FormSubmissionState>;
15
+ getState(request: AnyFormRequest): Promise<FormSubmissionState>;
17
16
  /**
18
17
  * Get the uploaded files from state.
19
18
  */
@@ -22,8 +21,8 @@ export declare class FileUploadPageController extends QuestionPageController {
22
21
  * Get the initiated upload from state.
23
22
  */
24
23
  getUploadFromState(state: FormSubmissionState): UploadInitiateResponse | undefined;
25
- makeGetItemDeleteRouteHandler(): (request: FormRequest, context: FormContext, h: Pick<ResponseToolkit, "redirect" | "view">) => import("@hapi/hapi").ResponseObject;
26
- makePostItemDeleteRouteHandler(): (request: FormRequestPayload, context: FormContext, h: Pick<ResponseToolkit, "redirect" | "view">) => Promise<import("@hapi/hapi").ResponseObject>;
24
+ makeGetItemDeleteRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => import("@hapi/hapi").ResponseObject;
25
+ makePostItemDeleteRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
27
26
  getErrors(details?: ValidationErrorItem[]): FormSubmissionError[] | undefined;
28
27
  getViewModel(request: FormContextRequest, context: FormContext): FeaturedFormPageViewModel;
29
28
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","server","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit } from '@hapi/hapi'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type FeaturedFormPageViewModel,\n type FileState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ uploadId }) => uploadId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const error = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n error,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const cacheService = getCacheService(request.server)\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","FileUploadField","at","length","badImplementation","path","indexOf","name","viewName","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","uploadId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","complete","unshift","mergeState","cacheService","server","setFlash","filesUpdated","options","schema","max","Math","min","outputEmail","def","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n tempItemSchema,\n type FileUploadField\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\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 type FormResponseToolkit\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: AnyFormRequest) {\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: FormResponseToolkit\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: FormResponseToolkit\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: AnyFormRequest,\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: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n const statusResponse = await getUploadStatus(uploadId)\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const error = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n error,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // by adding a 'multiple' attribute or it being\n // changed to a simple text field or similar.\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n const fileState = validationResult.value as FileState\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const file = fileState.status.form.file\n if (file.fileStatus === FileStatus.complete) {\n files.unshift(prepareFileState(fileState))\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n } else {\n // Flash the error message.\n const { fileUpload } = this\n const cacheService = getCacheService(request.server)\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n const errors: FormSubmissionError[] = [\n { path: [name], href: `#${name}`, name, text }\n ]\n cacheService.setFlash(request, { errors })\n }\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ uploadId }) => uploadId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const outputEmail =\n this.model.def.outputEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(href, outputEmail, options.accept)\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,10 +1,10 @@
1
1
  import { type Events, type FormDefinition, type Page, type Section } from '@defra/forms-model';
2
- import { type Lifecycle, type ResponseToolkit, type RouteOptions, type Server } from '@hapi/hapi';
2
+ import { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi';
3
3
  import { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js';
4
4
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
5
5
  import { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js';
6
6
  import { type FormContext, type PageViewModelBase } from '~/src/server/plugins/engine/types.js';
7
- import { type FormRequest, type FormRequestPayload, type FormRequestPayloadRefs, type FormRequestRefs } from '~/src/server/routes/types.js';
7
+ import { type FormRequest, type FormRequestPayload, type FormRequestPayloadRefs, type FormRequestRefs, type FormResponseToolkit } from '~/src/server/routes/types.js';
8
8
  export declare class PageController {
9
9
  /**
10
10
  * 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*}`.
@@ -19,7 +19,7 @@ export declare class PageController {
19
19
  events?: Events;
20
20
  collection?: ComponentCollection;
21
21
  viewName: string;
22
- allowSaveAndReturn: boolean;
22
+ allowSaveAndExit: boolean;
23
23
  constructor(model: FormModel, pageDef: Page);
24
24
  get path(): string;
25
25
  get href(): string;
@@ -39,7 +39,7 @@ export declare class PageController {
39
39
  getStartPath(): string;
40
40
  getSummaryPath(): string;
41
41
  getStatusPath(): string;
42
- makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: Pick<ResponseToolkit, 'redirect' | 'view'>) => ReturnType<Lifecycle.Method<FormRequestRefs>>;
43
- makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: Pick<ResponseToolkit, 'redirect' | 'view'>) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>>;
44
- shouldShowSaveAndReturn(server: Server): boolean;
42
+ makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => ReturnType<Lifecycle.Method<FormRequestRefs>>;
43
+ makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>>;
44
+ shouldShowSaveAndExit(server: Server): boolean;
45
45
  }
@@ -1,6 +1,6 @@
1
1
  import { ControllerPath } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
- import { encodeUrl, getSaveAndReturnHelpers, getStartPath, normalisePath } from "../helpers.js";
3
+ import { encodeUrl, getSaveAndExitHelpers, getStartPath, normalisePath } from "../helpers.js";
4
4
  export class PageController {
5
5
  /**
6
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*}`.
@@ -15,7 +15,7 @@ export class PageController {
15
15
  events;
16
16
  collection;
17
17
  viewName = 'index';
18
- allowSaveAndReturn = false;
18
+ allowSaveAndExit = false;
19
19
  constructor(model, pageDef) {
20
20
  const {
21
21
  def
@@ -138,8 +138,8 @@ export class PageController {
138
138
  makePostRouteHandler() {
139
139
  throw Boom.badRequest('Unsupported POST route handler for this page');
140
140
  }
141
- shouldShowSaveAndReturn(server) {
142
- return getSaveAndReturnHelpers(server) !== undefined && this.allowSaveAndReturn;
141
+ shouldShowSaveAndExit(server) {
142
+ return getSaveAndExitHelpers(server) !== undefined && this.allowSaveAndExit;
143
143
  }
144
144
  }
145
145
  //# sourceMappingURL=PageController.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getSaveAndReturnHelpers","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","allowSaveAndReturn","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","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest","shouldShowSaveAndReturn","server","undefined"],"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 type Server\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getSaveAndReturnHelpers,\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 allowSaveAndReturn = false\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): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\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 shouldShowSaveAndReturn(server: Server): boolean {\n return (\n getSaveAndReturnHelpers(server) !== undefined && this.allowSaveAndReturn\n )\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getSaveAndExitHelpers","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","allowSaveAndExit","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","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest","shouldShowSaveAndExit","server","undefined"],"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 { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getSaveAndExitHelpers,\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 type FormResponseToolkit\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 allowSaveAndExit = false\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): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\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: FormResponseToolkit\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: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n\n shouldShowSaveAndExit(server: Server): boolean {\n return getSaveAndExitHelpers(server) !== undefined && this.allowSaveAndExit\n }\n}\n"],"mappings":"AAAA,SACEA,cAAc,QAKT,oBAAoB;AAC3B,OAAOC,IAAI,MAAM,YAAY;AAI7B,SACEC,SAAS,EACTC,qBAAqB,EACrBC,YAAY,EACZC,aAAa;AAgBf,OAAO,MAAMC,cAAc,CAAC;EAC1B;AACF;AACA;EACEC,GAAG;EACHC,IAAI;EACJC,KAAK;EACLC,OAAO;EACPC,KAAK;EACLC,OAAO;EACPC,SAAS;EACTC,MAAM;EACNC,UAAU;EACVC,QAAQ,GAAG,OAAO;EAClBC,gBAAgB,GAAG,KAAK;EAExBC,WAAWA,CAACT,KAAgB,EAAEC,OAAa,EAAE;IAC3C,MAAM;MAAEH;IAAI,CAAC,GAAGE,KAAK;IAErB,IAAI,CAACF,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,IAAI,GAAGD,GAAG,CAACC,IAAI;IACpB,IAAI,CAACC,KAAK,GAAGA,KAAK;IAClB,IAAI,CAACC,OAAO,GAAGA,OAAO;IACtB,IAAI,CAACC,KAAK,GAAGD,OAAO,CAACC,KAAK;IAC1B,IAAI,CAACG,MAAM,GAAGJ,OAAO,CAACI,MAAM;;IAE5B;IACA,IAAI,CAACF,OAAO,GAAGH,KAAK,CAACU,QAAQ,CAACC,IAAI,CAC/BR,OAAO,IAAKA,OAAO,CAACJ,IAAI,KAAKE,OAAO,CAACE,OACxC,CAAC;;IAED;IACA,IAAIF,OAAO,CAACG,SAAS,EAAE;MACrB,IAAI,CAACA,SAAS,GAAGJ,KAAK,CAACY,UAAU,CAACX,OAAO,CAACG,SAAS,CAAC;IACtD;;IAEA;IACA,IAAIH,OAAO,CAACY,IAAI,EAAE;MAChB,IAAI,CAACN,QAAQ,GAAGN,OAAO,CAACY,IAAI;IAC9B;EACF;EAEA,IAAIC,IAAIA,CAAA,EAAG;IACT,OAAO,IAAI,CAACb,OAAO,CAACa,IAAI;EAC1B;EAEA,IAAIC,IAAIA,CAAA,EAAG;IACT,MAAM;MAAED;IAAK,CAAC,GAAG,IAAI;IACrB,OAAO,IAAI,CAACE,OAAO,CAAC,IAAIpB,aAAa,CAACkB,IAAI,CAAC,EAAE,CAAC;EAChD;EAEA,IAAIG,IAAIA,CAAA,EAAG;IACT,OAAO,IAAI,CAACX,UAAU,EAAEW,IAAI,IAAI,EAAE;EACpC;;EAEA;AACF;AACA;EACE,IAAIC,eAAeA,CAAA,EAAkC;IACnD,OAAO,CAAC,CAAC;EACX;;EAEA;AACF;AACA;EACE,IAAIC,gBAAgBA,CAAA,EAAyC;IAC3D,OAAO,CAAC,CAAC;EACX;EAEA,IAAIC,SAASA,CAAA,EAAsB;IACjC,MAAM;MAAErB,IAAI;MAAEI,OAAO;MAAED;IAAM,CAAC,GAAG,IAAI;IAErC,MAAMmB,SAAS,GAAG,IAAI;IACtB,MAAMC,SAAS,GAAGpB,KAAK;IACvB,MAAMqB,YAAY,GAAGpB,OAAO,EAAEqB,SAAS,KAAK,IAAI,GAAGrB,OAAO,EAAED,KAAK,GAAG,EAAE;IAEtE,OAAO;MACLH,IAAI;MACJ0B,IAAI,EAAE,IAAI;MACVH,SAAS;MACTC,YAAY;MACZF,SAAS;MACTK,WAAW,EAAE,KAAK;MAClBC,UAAU,EAAE,IAAI,CAACX,OAAO,CAAC,GAAG,CAAC;MAC7BY,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BC,QAAQ,EAAE,IAAI,CAACA;IACjB,CAAC;EACH;EAEA,IAAID,YAAYA,CAAA,EAAG;IACjB,MAAM;MAAE9B;IAAI,CAAC,GAAG,IAAI;;IAEpB;IACA,MAAM8B,YAAY,GAAG9B,GAAG,CAACgC,QAAQ,EAAEC,YAAY,GAC3C,UAAUjC,GAAG,CAACgC,QAAQ,CAACC,YAAY,EAAE,GACrCjC,GAAG,CAACgC,QAAQ,EAAEE,GAAG;IAErB,OAAOvC,SAAS,CAACmC,YAAY,CAAC;EAChC;EAEA,IAAIC,QAAQA,CAAA,EAAG;IACb,MAAM;MAAE/B;IAAI,CAAC,GAAG,IAAI;IACpB,OAAOA,GAAG,CAACmC,WAAW,EAAEC,KAAK;EAC/B;EAEAlB,OAAOA,CAACF,IAAY,EAAU;IAC5B,MAAMqB,QAAQ,GAAG,IAAI,CAACnC,KAAK,CAACmC,QAAQ;IAEpC,IAAIrB,IAAI,KAAK,GAAG,EAAE;MAChB,OAAO,IAAIqB,QAAQ,EAAE;IACvB;;IAEA;IACA,MAAMC,kBAAkB,GAAGtB,IAAI,CAACuB,UAAU,CAAC,GAAG,CAAC,GAAGvB,IAAI,CAACwB,SAAS,CAAC,CAAC,CAAC,GAAGxB,IAAI;IAC1E,IAAIyB,SAAS,GAAG,IAAIJ,QAAQ,EAAE;IAC9B,IAAIC,kBAAkB,EAAE;MACtBG,SAAS,IAAI,IAAIH,kBAAkB,EAAE;IACvC;IACAG,SAAS,GAAGA,SAAS,CAACC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;IAE7C,OAAOD,SAAS;EAClB;EAEA5C,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACK,KAAK,CAAC;EACjC;EAEAyC,cAAcA,CAAA,EAAG;IACf,OAAOlD,cAAc,CAACmD,OAAO,CAACC,OAAO,CAAC,CAAC;EACzC;EAEAC,aAAaA,CAAA,EAAG;IACd,OAAOrD,cAAc,CAACsD,MAAM,CAACF,OAAO,CAAC,CAAC;EACxC;EAEAG,mBAAmBA,CAAA,EAIgC;IACjD,OAAO,CAACC,OAAO,EAAEC,OAAO,EAAEC,CAAC,KAAK;MAC9B,MAAM;QAAE7B,SAAS;QAAEb;MAAS,CAAC,GAAG,IAAI;MACpC,OAAO0C,CAAC,CAACpC,IAAI,CAACN,QAAQ,EAAEa,SAAS,CAAC;IACpC,CAAC;EACH;EAEA8B,oBAAoBA,CAAA,EAIsC;IACxD,MAAM1D,IAAI,CAAC2D,UAAU,CAAC,8CAA8C,CAAC;EACvE;EAEAC,qBAAqBA,CAACC,MAAc,EAAW;IAC7C,OAAO3D,qBAAqB,CAAC2D,MAAM,CAAC,KAAKC,SAAS,IAAI,IAAI,CAAC9C,gBAAgB;EAC7E;AACF","ignoreList":[]}
@@ -1,16 +1,16 @@
1
1
  import { type Link, type Page } from '@defra/forms-model';
2
- import { type ResponseToolkit, type RouteOptions } from '@hapi/hapi';
2
+ import { type RouteOptions } from '@hapi/hapi';
3
3
  import { type ValidationErrorItem } from 'joi';
4
4
  import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js';
5
5
  import { type BackLink } from '~/src/server/plugins/engine/components/types.js';
6
6
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
7
7
  import { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
8
- import { type FormContext, type FormContextRequest, type FormPageViewModel, type FormPayload, type FormPayloadParams, type FormState, type FormStateValue, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
9
- import { type FormRequest, type FormRequestPayload, type FormRequestPayloadRefs, type FormRequestRefs } from '~/src/server/routes/types.js';
8
+ import { type AnyFormRequest, type FormContext, type FormContextRequest, type FormPageViewModel, type FormPayload, type FormPayloadParams, type FormState, type FormStateValue, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
9
+ import { type FormRequest, type FormRequestPayload, type FormRequestPayloadRefs, type FormRequestRefs, type FormResponseToolkit } from '~/src/server/routes/types.js';
10
10
  export declare class QuestionPageController extends PageController {
11
11
  collection: ComponentCollection;
12
12
  errorSummaryTitle: string;
13
- allowSaveAndReturn: boolean;
13
+ allowSaveAndExit: boolean;
14
14
  constructor(model: FormModel, pageDef: Page);
15
15
  get next(): Link[];
16
16
  get allowContinue(): boolean;
@@ -21,7 +21,7 @@ export declare class QuestionPageController extends PageController {
21
21
  * @param context - the form context
22
22
  */
23
23
  getViewModel(request: FormContextRequest, context: FormContext): FormPageViewModel;
24
- getRelevantPath(request: FormRequest | FormRequestPayload, context: FormContext): string;
24
+ getRelevantPath(request: AnyFormRequest, context: FormContext): string;
25
25
  /**
26
26
  * Apply conditions to evaluation state to determine next page path
27
27
  */
@@ -36,22 +36,22 @@ export declare class QuestionPageController extends PageController {
36
36
  getFormParams(request?: FormContextRequest): FormPayloadParams;
37
37
  getStateFromValidForm(request: FormContextRequest, state: FormSubmissionState, payload: FormPayload): FormState;
38
38
  getErrors(details?: ValidationErrorItem[]): import("~/src/server/plugins/engine/types.js").FormSubmissionError[] | undefined;
39
- getState(request: FormRequest | FormRequestPayload): Promise<FormSubmissionState>;
40
- setState(request: FormRequest | FormRequestPayload, state: FormSubmissionState): Promise<FormSubmissionState>;
41
- mergeState(request: FormRequest | FormRequestPayload, state: FormSubmissionState, update: object): Promise<FormSubmissionState>;
39
+ getState(request: AnyFormRequest): Promise<FormSubmissionState>;
40
+ setState(request: AnyFormRequest, state: FormSubmissionState): Promise<FormSubmissionState>;
41
+ mergeState(request: AnyFormRequest, state: FormSubmissionState, update: object): Promise<FormSubmissionState>;
42
42
  filterConditionalComponents(viewModel: FormPageViewModel, model: FormModel, evaluationState: Partial<Record<string, FormStateValue>>): import("~/src/server/plugins/engine/components/types.js").ComponentViewModel[];
43
- makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: Pick<ResponseToolkit, "redirect" | "view">) => Promise<import("@hapi/hapi").ResponseObject>;
43
+ makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
44
44
  hasMissingNotificationEmail(request: FormRequest, context: FormContext): Promise<boolean>;
45
45
  /**
46
46
  * Get the back link for a given progress.
47
47
  */
48
48
  protected getBackLink(request: FormContextRequest, context: FormContext): BackLink | undefined;
49
- makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: Pick<ResponseToolkit, "redirect" | "view">) => Promise<import("@hapi/hapi").ResponseObject>;
50
- proceed(request: FormContextRequest, h: Pick<ResponseToolkit, 'redirect' | 'view'>, nextPath?: string): import("@hapi/hapi").ResponseObject;
49
+ makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
50
+ proceed(request: FormContextRequest, h: FormResponseToolkit, nextPath?: string): import("@hapi/hapi").ResponseObject;
51
51
  /**
52
- * Handle save-and-return action by processing form data and redirecting to exit page
52
+ * Handle save-and-exit action
53
53
  */
54
- handleSaveAndReturn(request: FormRequestPayload, context: FormContext, h: Pick<ResponseToolkit, 'redirect' | 'view'>): Promise<import("@hapi/hapi").ResponseObject>;
54
+ handleSaveAndExit(request: FormRequestPayload, context: FormContext, h: FormResponseToolkit): import("@hapi/hapi").ResponseObject;
55
55
  /**
56
56
  * {@link https://hapi.dev/api/?v=20.1.2#route-options}
57
57
  */
@@ -2,7 +2,7 @@ import { ComponentType, ControllerType, Engine, hasComponents, hasNext, hasRepea
2
2
  import Boom from '@hapi/boom';
3
3
  import { ComponentCollection } from "../components/ComponentCollection.js";
4
4
  import { optionalText } from "../components/constants.js";
5
- import { getCacheService, getErrors, getSaveAndReturnHelpers, normalisePath, proceed } from "../helpers.js";
5
+ import { getCacheService, getErrors, getSaveAndExitHelpers, normalisePath, proceed } from "../helpers.js";
6
6
  import { PageController } from "./PageController.js";
7
7
  import { FormAction } from "../../../routes/types.js";
8
8
  import { actionSchema, crumbSchema, paramsSchema } from "../../../schemas/index.js";
@@ -10,7 +10,7 @@ import { merge } from "../../../services/cacheService.js";
10
10
  export class QuestionPageController extends PageController {
11
11
  collection;
12
12
  errorSummaryTitle = 'There is a problem';
13
- allowSaveAndReturn = true;
13
+ allowSaveAndExit = true;
14
14
  constructor(model, pageDef) {
15
15
  super(model, pageDef);
16
16
 
@@ -136,7 +136,7 @@ export class QuestionPageController extends PageController {
136
136
  showTitle,
137
137
  components,
138
138
  errors,
139
- allowSaveAndReturn: this.shouldShowSaveAndReturn(request.server)
139
+ allowSaveAndExit: this.shouldShowSaveAndExit(request.server)
140
140
  };
141
141
  }
142
142
  getRelevantPath(request, context) {
@@ -430,12 +430,12 @@ export class QuestionPageController extends PageController {
430
430
  return h.view(viewName, viewModel);
431
431
  }
432
432
 
433
- // Check if this is a save-and-return action
433
+ // Check if this is a save-and-exit action
434
434
  const {
435
435
  action
436
436
  } = request.payload;
437
- if (action === FormAction.SaveAndReturn) {
438
- return this.handleSaveAndReturn(request, context, h);
437
+ if (action === FormAction.SaveAndExit) {
438
+ return this.handleSaveAndExit(request, context, h);
439
439
  }
440
440
 
441
441
  // Save and proceed
@@ -451,22 +451,14 @@ export class QuestionPageController extends PageController {
451
451
  }
452
452
 
453
453
  /**
454
- * Handle save-and-return action by processing form data and redirecting to exit page
454
+ * Handle save-and-exit action
455
455
  */
456
- async handleSaveAndReturn(request, context, h) {
457
- const {
458
- state
459
- } = context;
460
-
461
- // Save the current state and redirect to exit page
462
- const saveAndReturn = getSaveAndReturnHelpers(request.server);
463
- if (!saveAndReturn?.sessionPersister) {
464
- throw Boom.internal('Server misconfigured for save and return');
456
+ handleSaveAndExit(request, context, h) {
457
+ const saveAndExit = getSaveAndExitHelpers(request.server);
458
+ if (!saveAndExit) {
459
+ throw Boom.internal('Server misconfigured for save and exit');
465
460
  }
466
- await saveAndReturn.sessionPersister(state, request);
467
- const cacheService = getCacheService(request.server);
468
- await cacheService.clearState(request);
469
- return h.redirect(this.getHref('/exit'));
461
+ return saveAndExit(request, h, context);
470
462
  }
471
463
 
472
464
  /**