@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.
- package/.server/server/index.js +2 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/README.md +2 -2
- package/.server/server/plugins/engine/configureEnginePlugin.d.ts +2 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +4 -4
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +7 -11
- package/.server/server/plugins/engine/helpers.js +2 -2
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +1 -1
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/options.js +3 -6
- package/.server/server/plugins/engine/options.js.map +1 -1
- package/.server/server/plugins/engine/options.test.js +2 -8
- package/.server/server/plugins/engine/options.test.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +5 -6
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.d.ts +6 -6
- package/.server/server/plugins/engine/pageControllers/PageController.js +4 -4
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +13 -13
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +12 -20
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +7 -8
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
- package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +3 -4
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +5 -4
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +12 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +4 -4
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -1
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +2 -6
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +7 -12
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.d.ts +5 -5
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.d.ts +4 -4
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -1
- package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +2 -2
- package/.server/server/plugins/engine/types/index.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +10 -11
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/partials/form.html +3 -3
- package/.server/server/plugins/engine/views/summary.html +21 -5
- package/.server/server/plugins/nunjucks/context.d.ts +5 -6
- package/.server/server/plugins/nunjucks/context.js +3 -3
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/routes/types.d.ts +3 -2
- package/.server/server/routes/types.js +1 -1
- package/.server/server/routes/types.js.map +1 -1
- package/.server/server/schemas/index.js +1 -1
- package/.server/server/schemas/index.js.map +1 -1
- package/.server/server/services/cacheService.d.ts +11 -19
- package/.server/server/services/cacheService.js +9 -30
- package/.server/server/services/cacheService.js.map +1 -1
- package/.server/server/types.d.ts +4 -1
- package/.server/server/types.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +3 -1
- package/src/server/index.test.ts +0 -39
- package/src/server/index.ts +4 -1
- package/src/server/plugins/engine/README.md +2 -2
- package/src/server/plugins/engine/components/helpers/helpers.test.ts +1 -1
- package/src/server/plugins/engine/configureEnginePlugin.ts +15 -11
- package/src/server/plugins/engine/helpers.test.ts +3 -2
- package/src/server/plugins/engine/helpers.ts +6 -6
- package/src/server/plugins/engine/models/FormModel.test.ts +2 -2
- package/src/server/plugins/engine/models/FormModel.ts +1 -2
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -7
- package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
- package/src/server/plugins/engine/options.js +6 -6
- package/src/server/plugins/engine/options.test.js +2 -6
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +8 -10
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +9 -8
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +11 -8
- package/src/server/plugins/engine/pageControllers/PageController.ts +9 -15
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +35 -102
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +24 -36
- package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +4 -6
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -11
- package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +4 -4
- package/src/server/plugins/engine/pageControllers/StartPageController.ts +1 -1
- package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +4 -4
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +6 -4
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -5
- package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +4 -4
- package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +7 -4
- package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +5 -6
- package/src/server/plugins/engine/plugin.ts +7 -13
- package/src/server/plugins/engine/routes/index.ts +6 -11
- package/src/server/plugins/engine/routes/questions.test.ts +29 -53
- package/src/server/plugins/engine/routes/questions.ts +6 -8
- package/src/server/plugins/engine/routes/repeaters/item-delete.ts +5 -14
- package/src/server/plugins/engine/routes/repeaters/summary.ts +5 -14
- package/src/server/plugins/engine/types/index.ts +4 -1
- package/src/server/plugins/engine/types.ts +19 -13
- package/src/server/plugins/engine/views/partials/form.html +3 -3
- package/src/server/plugins/engine/views/summary.html +21 -5
- package/src/server/plugins/nunjucks/context.js +3 -3
- package/src/server/routes/types.ts +7 -2
- package/src/server/schemas/index.ts +1 -1
- package/src/server/services/cacheService.test.ts +1 -117
- package/src/server/services/cacheService.ts +22 -73
- package/src/server/types.ts +4 -1
- package/src/typings/hapi/index.d.ts +6 -7
- package/.server/server/plugins/engine/routes/exit.d.ts +0 -46
- package/.server/server/plugins/engine/routes/exit.js +0 -36
- package/.server/server/plugins/engine/routes/exit.js.map +0 -1
- package/src/server/plugins/engine/routes/exit.ts +0 -47
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
|
|
4
4
|
{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
|
|
5
|
+
{% from "govuk/components/button/macro.njk" import govukButton %}
|
|
5
6
|
{% from "partials/components.html" import componentList with context %}
|
|
6
7
|
|
|
7
8
|
{% block content %}
|
|
@@ -31,7 +32,6 @@
|
|
|
31
32
|
|
|
32
33
|
<form method="post" novalidate>
|
|
33
34
|
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
34
|
-
<input type="hidden" name="action" value="send">
|
|
35
35
|
|
|
36
36
|
{% if declaration %}
|
|
37
37
|
<h2 class="govuk-heading-m" id="declaration">Declaration</h2>
|
|
@@ -42,10 +42,26 @@
|
|
|
42
42
|
|
|
43
43
|
{{ componentList(components) }}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
<div class="govuk-button-group">
|
|
46
|
+
{% set isDeclaration = declaration or components | length %}
|
|
47
|
+
|
|
48
|
+
{{ govukButton({
|
|
49
|
+
text: "Accept and send" if isDeclaration else "Send",
|
|
50
|
+
name: "action",
|
|
51
|
+
value: "send",
|
|
52
|
+
preventDoubleClick: true
|
|
53
|
+
}) }}
|
|
54
|
+
|
|
55
|
+
{% if allowSaveAndExit %}
|
|
56
|
+
{{ govukButton({
|
|
57
|
+
text: "Save and exit",
|
|
58
|
+
classes: "govuk-button--secondary",
|
|
59
|
+
name: "action",
|
|
60
|
+
value: "save-and-exit",
|
|
61
|
+
preventDoubleClick: true
|
|
62
|
+
}) }}
|
|
63
|
+
{% endif %}
|
|
64
|
+
</div>
|
|
49
65
|
</form>
|
|
50
66
|
</div>
|
|
51
67
|
</div>
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {
|
|
2
|
+
* @param {AnyFormRequest | null} request
|
|
3
3
|
*/
|
|
4
|
-
export function context(request:
|
|
4
|
+
export function context(request: AnyFormRequest | null): Promise<ViewContext>;
|
|
5
5
|
/**
|
|
6
6
|
* Returns the context for the devtool. Consumers won't have access to this.
|
|
7
|
-
* @param {
|
|
7
|
+
* @param {AnyFormRequest | null} _request
|
|
8
8
|
* @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
|
|
9
9
|
*/
|
|
10
|
-
export function devtoolContext(_request:
|
|
10
|
+
export function devtoolContext(_request: AnyFormRequest | null): Record<string, unknown> & {
|
|
11
11
|
assetPath: string;
|
|
12
12
|
getDxtAssetPath: (asset: string) => string;
|
|
13
13
|
};
|
|
14
|
-
import type {
|
|
15
|
-
import type { FormRequestPayload } from '~/src/server/routes/types.js';
|
|
14
|
+
import type { AnyFormRequest } from '~/src/server/plugins/engine/types.js';
|
|
16
15
|
import type { ViewContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
@@ -11,7 +11,7 @@ const logger = createLogger();
|
|
|
11
11
|
let webpackManifest;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @param {
|
|
14
|
+
* @param {AnyFormRequest | null} request
|
|
15
15
|
*/
|
|
16
16
|
export async function context(request) {
|
|
17
17
|
const {
|
|
@@ -53,7 +53,7 @@ export async function context(request) {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Returns the context for the devtool. Consumers won't have access to this.
|
|
56
|
-
* @param {
|
|
56
|
+
* @param {AnyFormRequest | null} _request
|
|
57
57
|
* @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
|
|
58
58
|
*/
|
|
59
59
|
export function devtoolContext(_request) {
|
|
@@ -84,6 +84,6 @@ export function devtoolContext(_request) {
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
87
|
-
* @import {
|
|
87
|
+
* @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'
|
|
88
88
|
*/
|
|
89
89
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","crumb","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\n encodeUrl,\n safeGenerateCrumb\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {
|
|
1
|
+
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","crumb","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\n encodeUrl,\n safeGenerateCrumb\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {AnyFormRequest | null} request\n */\nexport async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\n\n // Only add the slug in to the context if the response is OK.\n // Footer meta links are not rendered when the slug is missing.\n const isResponseOK =\n !Boom.isBoom(response) && response?.statusCode === StatusCodes.OK\n\n const pluginStorage = request?.server.plugins['forms-engine-plugin']\n\n let consumerViewContext = {}\n\n if (!pluginStorage) {\n throw Error('context called before plugin registered')\n }\n\n if (!pluginStorage.baseLayoutPath) {\n throw Error('Missing baseLayoutPath in plugin.options.nunjucks')\n }\n\n if (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await pluginStorage.viewContext(request)\n }\n\n /** @type {ViewContext} */\n const ctx = {\n // take consumers props first so we can override it\n ...consumerViewContext,\n baseLayoutPath: pluginStorage.baseLayoutPath,\n crumb: safeGenerateCrumb(request),\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : undefined,\n slug: isResponseOK ? params?.slug : undefined\n }\n\n return ctx\n}\n\n/**\n * Returns the context for the devtool. Consumers won't have access to this.\n * @param {AnyFormRequest | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\n const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')\n\n if (!webpackManifest) {\n try {\n // eslint-disable-next-line -- Allow JSON type 'any'\n webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))\n } catch {\n logger.info(\n `[webpackManifestMissing] Webpack ${basename(manifestPath)} not found - running without asset manifest`\n )\n }\n }\n\n return {\n config: {\n cdpEnvironment: config.get('cdpEnvironment'),\n designerUrl: config.get('designerUrl'),\n feedbackLink: encodeUrl(config.get('feedbackLink')),\n phaseTag: config.get('phaseTag'),\n serviceName: config.get('serviceName'),\n serviceVersion: config.get('serviceVersion')\n },\n assetPath: '/assets',\n getDxtAssetPath: (asset = '') => {\n return `/${webpackManifest?.[asset] ?? asset}`\n }\n }\n}\n\n/**\n * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReqRefDefaults, type Request } from '@hapi/hapi';
|
|
1
|
+
import { type ReqRefDefaults, type Request, type ResponseToolkit } from '@hapi/hapi';
|
|
2
2
|
import { type FormPayload } from '~/src/server/plugins/engine/types.js';
|
|
3
3
|
export interface FormQuery extends Partial<Record<string, string>> {
|
|
4
4
|
/**
|
|
@@ -25,13 +25,14 @@ export interface FormRequestPayloadRefs extends FormRequestRefs {
|
|
|
25
25
|
}
|
|
26
26
|
export type FormRequest = Request<FormRequestRefs>;
|
|
27
27
|
export type FormRequestPayload = Request<FormRequestPayloadRefs>;
|
|
28
|
+
export type FormResponseToolkit = Pick<ResponseToolkit, 'redirect' | 'view'>;
|
|
28
29
|
export declare enum FormAction {
|
|
29
30
|
Continue = "continue",
|
|
30
31
|
Validate = "validate",
|
|
31
32
|
Delete = "delete",
|
|
32
33
|
AddAnother = "add-another",
|
|
33
34
|
Send = "send",
|
|
34
|
-
|
|
35
|
+
SaveAndExit = "save-and-exit"
|
|
35
36
|
}
|
|
36
37
|
export declare enum FormStatus {
|
|
37
38
|
Draft = "draft",
|
|
@@ -4,7 +4,7 @@ export let FormAction = /*#__PURE__*/function (FormAction) {
|
|
|
4
4
|
FormAction["Delete"] = "delete";
|
|
5
5
|
FormAction["AddAnother"] = "add-another";
|
|
6
6
|
FormAction["Send"] = "send";
|
|
7
|
-
FormAction["
|
|
7
|
+
FormAction["SaveAndExit"] = "save-and-exit";
|
|
8
8
|
return FormAction;
|
|
9
9
|
}({});
|
|
10
10
|
export let FormStatus = /*#__PURE__*/function (FormStatus) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["FormAction","FormStatus"],"sources":["../../../src/server/routes/types.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"types.js","names":["FormAction","FormStatus"],"sources":["../../../src/server/routes/types.ts"],"sourcesContent":["import {\n type ReqRefDefaults,\n type Request,\n type ResponseToolkit\n} from '@hapi/hapi'\n\nimport { type FormPayload } from '~/src/server/plugins/engine/types.js'\n\nexport interface FormQuery extends Partial<Record<string, string>> {\n /**\n * Allow preview URL direct access without relevant page checks\n */\n force?: string\n\n /**\n * Redirect location after 'continue' form action\n */\n returnUrl?: string\n}\n\nexport interface FormParams extends Partial<Record<string, string>> {\n path: string\n slug: string\n state?: FormStatus\n}\n\nexport interface FormRequestRefs\n extends Omit<ReqRefDefaults, 'Params' | 'Payload' | 'Query'> {\n Params: FormParams\n Payload: object | undefined\n Query: FormQuery\n}\n\nexport interface FormRequestPayloadRefs extends FormRequestRefs {\n Payload: FormPayload\n}\n\nexport type FormRequest = Request<FormRequestRefs>\nexport type FormRequestPayload = Request<FormRequestPayloadRefs>\nexport type FormResponseToolkit = Pick<ResponseToolkit, 'redirect' | 'view'>\n\nexport enum FormAction {\n Continue = 'continue',\n Validate = 'validate',\n Delete = 'delete',\n AddAnother = 'add-another',\n Send = 'send',\n SaveAndExit = 'save-and-exit'\n}\n\nexport enum FormStatus {\n Draft = 'draft',\n Live = 'live'\n}\n"],"mappings":"AAyCA,WAAYA,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA;AAStB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Joi from 'joi';
|
|
2
2
|
import { FormAction, FormStatus } from "../routes/types.js";
|
|
3
3
|
export const stateSchema = Joi.string().valid(FormStatus.Draft, FormStatus.Live).required();
|
|
4
|
-
export const actionSchema = Joi.string().valid(FormAction.Continue, FormAction.Validate, FormAction.Delete, FormAction.AddAnother, FormAction.Send, FormAction.
|
|
4
|
+
export const actionSchema = Joi.string().valid(FormAction.Continue, FormAction.Validate, FormAction.Delete, FormAction.AddAnother, FormAction.Send, FormAction.SaveAndExit).default(FormAction.Validate).optional();
|
|
5
5
|
export const pathSchema = Joi.string().required();
|
|
6
6
|
export const itemIdSchema = Joi.string().uuid().required();
|
|
7
7
|
export const crumbSchema = Joi.string().optional().allow('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","
|
|
1
|
+
{"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","SaveAndExit","default","optional","pathSchema","itemIdSchema","uuid","crumbSchema","allow","confirmSchema","boolean","empty","paramsSchema","object","keys","action","confirm","crumb","itemId"],"sources":["../../../src/server/schemas/index.ts"],"sourcesContent":["import Joi from 'joi'\n\nimport { type FormPayloadParams } from '~/src/server/plugins/engine/types.js'\nimport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport const stateSchema = Joi.string<FormStatus>()\n .valid(FormStatus.Draft, FormStatus.Live)\n .required()\n\nexport const actionSchema = Joi.string<FormAction>()\n .valid(\n FormAction.Continue,\n FormAction.Validate,\n FormAction.Delete,\n FormAction.AddAnother,\n FormAction.Send,\n FormAction.SaveAndExit\n )\n .default(FormAction.Validate)\n .optional()\n\nexport const pathSchema = Joi.string().required()\nexport const itemIdSchema = Joi.string().uuid().required()\nexport const crumbSchema = Joi.string().optional().allow('')\nexport const confirmSchema = Joi.boolean().empty(false)\n\nexport const paramsSchema = Joi.object<FormPayloadParams>()\n .keys({\n action: actionSchema,\n confirm: confirmSchema,\n crumb: crumbSchema,\n itemId: itemIdSchema.optional()\n })\n .default({})\n .optional()\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAGrB,SAASC,UAAU,EAAEC,UAAU;AAE/B,OAAO,MAAMC,WAAW,GAAGH,GAAG,CAACI,MAAM,CAAa,CAAC,CAChDC,KAAK,CAACH,UAAU,CAACI,KAAK,EAAEJ,UAAU,CAACK,IAAI,CAAC,CACxCC,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,YAAY,GAAGT,GAAG,CAACI,MAAM,CAAa,CAAC,CACjDC,KAAK,CACJJ,UAAU,CAACS,QAAQ,EACnBT,UAAU,CAACU,QAAQ,EACnBV,UAAU,CAACW,MAAM,EACjBX,UAAU,CAACY,UAAU,EACrBZ,UAAU,CAACa,IAAI,EACfb,UAAU,CAACc,WACb,CAAC,CACAC,OAAO,CAACf,UAAU,CAACU,QAAQ,CAAC,CAC5BM,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,UAAU,GAAGlB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACI,QAAQ,CAAC,CAAC;AACjD,OAAO,MAAMW,YAAY,GAAGnB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACgB,IAAI,CAAC,CAAC,CAACZ,QAAQ,CAAC,CAAC;AAC1D,OAAO,MAAMa,WAAW,GAAGrB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACa,QAAQ,CAAC,CAAC,CAACK,KAAK,CAAC,EAAE,CAAC;AAC5D,OAAO,MAAMC,aAAa,GAAGvB,GAAG,CAACwB,OAAO,CAAC,CAAC,CAACC,KAAK,CAAC,KAAK,CAAC;AAEvD,OAAO,MAAMC,YAAY,GAAG1B,GAAG,CAAC2B,MAAM,CAAoB,CAAC,CACxDC,IAAI,CAAC;EACJC,MAAM,EAAEpB,YAAY;EACpBqB,OAAO,EAAEP,aAAa;EACtBQ,KAAK,EAAEV,WAAW;EAClBW,MAAM,EAAEb,YAAY,CAACF,QAAQ,CAAC;AAChC,CAAC,CAAC,CACDD,OAAO,CAAC,CAAC,CAAC,CAAC,CACXC,QAAQ,CAAC,CAAC","ignoreList":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type FormPayload, type FormState, type FormSubmissionError, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
|
|
3
|
-
import { type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
|
|
1
|
+
import { type Server } from '@hapi/hapi';
|
|
2
|
+
import { type AnyFormRequest, type AnyRequest, type FormPayload, type FormState, type FormSubmissionError, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
|
|
4
3
|
export declare enum ADDITIONAL_IDENTIFIER {
|
|
5
4
|
Confirmation = ":confirmation"
|
|
6
5
|
}
|
|
@@ -12,40 +11,33 @@ export declare class CacheService {
|
|
|
12
11
|
cache: string | undefined;
|
|
13
12
|
segment: string;
|
|
14
13
|
}>;
|
|
15
|
-
generateKey?: (request: Request | FormRequest | FormRequestPayload) => string;
|
|
16
|
-
customFetcher?: (request: Request | FormRequest | FormRequestPayload) => Promise<FormSubmissionState | null>;
|
|
17
14
|
logger: Server['logger'];
|
|
18
|
-
constructor({ server, cacheName
|
|
15
|
+
constructor({ server, cacheName }: {
|
|
19
16
|
server: Server;
|
|
20
17
|
cacheName?: string;
|
|
21
|
-
options?: {
|
|
22
|
-
keyGenerator?: (request: Request | FormRequest | FormRequestPayload) => string;
|
|
23
|
-
sessionHydrator?: (request: Request | FormRequest | FormRequestPayload) => Promise<FormSubmissionState | null>;
|
|
24
|
-
};
|
|
25
18
|
});
|
|
26
|
-
getState(request:
|
|
27
|
-
setState(request:
|
|
28
|
-
getConfirmationState(request:
|
|
19
|
+
getState(request: AnyRequest): Promise<FormSubmissionState>;
|
|
20
|
+
setState(request: AnyFormRequest, state: FormSubmissionState): Promise<FormSubmissionState>;
|
|
21
|
+
getConfirmationState(request: AnyFormRequest): Promise<{
|
|
29
22
|
confirmed?: true;
|
|
30
23
|
}>;
|
|
31
|
-
setConfirmationState(request:
|
|
24
|
+
setConfirmationState(request: AnyFormRequest, confirmationState: {
|
|
32
25
|
confirmed?: true;
|
|
33
26
|
}): Promise<void>;
|
|
34
|
-
clearState(request:
|
|
35
|
-
getFlash(request:
|
|
27
|
+
clearState(request: AnyFormRequest): Promise<void>;
|
|
28
|
+
getFlash(request: AnyFormRequest): {
|
|
36
29
|
errors: FormSubmissionError[];
|
|
37
30
|
} | undefined;
|
|
38
|
-
setFlash(request:
|
|
31
|
+
setFlash(request: AnyFormRequest, message: {
|
|
39
32
|
errors: FormSubmissionError[];
|
|
40
33
|
}): void;
|
|
41
|
-
private defaultKeyGenerator;
|
|
42
34
|
/**
|
|
43
35
|
* The key used to store user session data against.
|
|
44
36
|
* If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`
|
|
45
37
|
* @param request - hapi request object
|
|
46
38
|
* @param additionalIdentifier - appended to the id
|
|
47
39
|
*/
|
|
48
|
-
Key(request:
|
|
40
|
+
Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER): {
|
|
49
41
|
segment: string;
|
|
50
42
|
id: string;
|
|
51
43
|
};
|
|
@@ -10,23 +10,14 @@ export class CacheService {
|
|
|
10
10
|
* This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}
|
|
11
11
|
*/
|
|
12
12
|
cache;
|
|
13
|
-
generateKey;
|
|
14
|
-
customFetcher;
|
|
15
13
|
logger;
|
|
16
14
|
constructor({
|
|
17
15
|
server,
|
|
18
|
-
cacheName
|
|
19
|
-
options
|
|
16
|
+
cacheName
|
|
20
17
|
}) {
|
|
21
|
-
const {
|
|
22
|
-
keyGenerator,
|
|
23
|
-
sessionHydrator
|
|
24
|
-
} = options ?? {};
|
|
25
18
|
if (!cacheName) {
|
|
26
19
|
server.log('warn', 'You are using the default hapi cache. Please provide a cache name in plugin registration options.');
|
|
27
20
|
}
|
|
28
|
-
this.generateKey = keyGenerator ?? this.defaultKeyGenerator.bind(this);
|
|
29
|
-
this.customFetcher = sessionHydrator ?? undefined;
|
|
30
21
|
this.cache = server.cache({
|
|
31
22
|
cache: cacheName,
|
|
32
23
|
segment: 'formSubmission'
|
|
@@ -35,16 +26,7 @@ export class CacheService {
|
|
|
35
26
|
}
|
|
36
27
|
async getState(request) {
|
|
37
28
|
const key = this.Key(request);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// If nothing in Redis, attempt to rehydrate from backend DB
|
|
41
|
-
if (!cached && this.customFetcher) {
|
|
42
|
-
const rehydrated = await this.customFetcher(request);
|
|
43
|
-
if (rehydrated != null) {
|
|
44
|
-
await this.cache.set(key, rehydrated, config.get('sessionTimeout'));
|
|
45
|
-
cached = await this.getState(request);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
29
|
+
const cached = await this.cache.get(key);
|
|
48
30
|
return cached ?? {};
|
|
49
31
|
}
|
|
50
32
|
async setState(request, state) {
|
|
@@ -79,14 +61,6 @@ export class CacheService {
|
|
|
79
61
|
const key = this.Key(request);
|
|
80
62
|
request.yar.flash(key.id, message);
|
|
81
63
|
}
|
|
82
|
-
defaultKeyGenerator(request) {
|
|
83
|
-
if (!request.yar.id) {
|
|
84
|
-
throw new Error('No session ID found');
|
|
85
|
-
}
|
|
86
|
-
const state = request.params.state || '';
|
|
87
|
-
const slug = request.params.slug || '';
|
|
88
|
-
return `${request.yar.id}:${state}:${slug}:`;
|
|
89
|
-
}
|
|
90
64
|
|
|
91
65
|
/**
|
|
92
66
|
* The key used to store user session data against.
|
|
@@ -95,10 +69,15 @@ export class CacheService {
|
|
|
95
69
|
* @param additionalIdentifier - appended to the id
|
|
96
70
|
*/
|
|
97
71
|
Key(request, additionalIdentifier) {
|
|
98
|
-
|
|
72
|
+
if (!request.yar.id) {
|
|
73
|
+
throw new Error('No session ID found');
|
|
74
|
+
}
|
|
75
|
+
const state = request.params.state || '';
|
|
76
|
+
const slug = request.params.slug || '';
|
|
77
|
+
const key = `${request.yar.id}:${state}:${slug}:`;
|
|
99
78
|
return {
|
|
100
79
|
segment: partition,
|
|
101
|
-
id: `${
|
|
80
|
+
id: `${key}${additionalIdentifier ?? ''}`
|
|
102
81
|
};
|
|
103
82
|
}
|
|
104
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","
|
|
1
|
+
{"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","key","Key","cached","get","setState","state","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type AnyFormRequest,\n type AnyRequest,\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor({ server, cacheName }: { server: Server; cacheName?: string }) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(request: AnyRequest): Promise<FormSubmissionState> {\n const key = this.Key(request)\n const cached = await this.cache.get(key)\n\n return cached ?? {}\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: AnyFormRequest\n ): Promise<{ confirmed?: true }> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: AnyFormRequest,\n confirmationState: { confirmed?: true }\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: AnyFormRequest) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: AnyFormRequest\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0) as { errors: FormSubmissionError[] }\n }\n }\n\n setFlash(\n request: AnyFormRequest,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n const key = `${request.yar.id}:${state}:${slug}:`\n\n return {\n segment: partition,\n id: `${key}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type FormDefinition, type FormMetadata, type SubmitPayload, type SubmitResponsePayload } from '@defra/forms-model';
|
|
2
|
+
import { type Server } from '@hapi/hapi';
|
|
2
3
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
3
4
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
|
|
4
5
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
|
|
5
6
|
import { type FormContext, type OnRequestCallback, type PluginOptions, type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
|
|
6
7
|
import { type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
|
|
8
|
+
import { type CacheService } from '~/src/server/services/cacheService.js';
|
|
7
9
|
export interface FormsService {
|
|
8
10
|
getFormMetadata: (slug: string) => Promise<FormMetadata>;
|
|
9
11
|
getFormDefinition: (id: string, state: FormStatus) => Promise<FormDefinition | undefined>;
|
|
@@ -28,7 +30,8 @@ export interface RouteConfig {
|
|
|
28
30
|
controllers?: Record<string, typeof PageController>;
|
|
29
31
|
preparePageEventRequestOptions?: PreparePageEventRequestOptions;
|
|
30
32
|
onRequest?: OnRequestCallback;
|
|
31
|
-
|
|
33
|
+
saveAndExit?: PluginOptions['saveAndExit'];
|
|
34
|
+
cacheServiceCreator?: (server: Server) => CacheService;
|
|
32
35
|
}
|
|
33
36
|
export interface OutputService {
|
|
34
37
|
submit: (context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, formMetadata?: FormMetadata) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\nimport { type Server } from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndExit?: PluginOptions['saveAndExit']\n cacheServiceCreator?: (server: Server) => CacheService\n}\n\nexport interface OutputService {\n submit: (\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module '@hapi/scooter' {\n declare const hapiScooter: {\n plugin: Plugin\n }\n\n export = hapiScooter\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"./services/*": "./.server/server/plugins/engine/services/*",
|
|
29
29
|
"./engine/*": "./.server/server/plugins/engine/*",
|
|
30
30
|
"./helpers.js": "./.server/server/plugins/engine/components/helpers.js",
|
|
31
|
+
"./schema.js": "./.server/server/schemas/index.js",
|
|
31
32
|
"./templates/*": "./.server/server/plugins/engine/views/*",
|
|
33
|
+
"./cache-service.js": "./.server/server/services/cacheService.js",
|
|
32
34
|
"./package.json": "./package.json"
|
|
33
35
|
},
|
|
34
36
|
"scripts": {
|
package/src/server/index.test.ts
CHANGED
|
@@ -639,42 +639,3 @@ describe('prepareEnvironment', () => {
|
|
|
639
639
|
)
|
|
640
640
|
})
|
|
641
641
|
})
|
|
642
|
-
|
|
643
|
-
describe('Exit route handlers', () => {
|
|
644
|
-
let server: Server
|
|
645
|
-
|
|
646
|
-
beforeAll(async () => {
|
|
647
|
-
server = await createServer({
|
|
648
|
-
services: defaultServices
|
|
649
|
-
})
|
|
650
|
-
await server.initialize()
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
afterAll(async () => {
|
|
654
|
-
await server.stop()
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
beforeEach(() => {
|
|
658
|
-
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
|
|
659
|
-
server.app.models.clear()
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
test('GET /exit returns 200 with exit page content', async () => {
|
|
663
|
-
jest.mocked(getFormMetadata).mockResolvedValueOnce({
|
|
664
|
-
...fixtures.form.metadata,
|
|
665
|
-
live: fixtures.form.state
|
|
666
|
-
})
|
|
667
|
-
|
|
668
|
-
jest.mocked(getFormDefinition).mockResolvedValue(fixtures.form.definition)
|
|
669
|
-
|
|
670
|
-
const options = {
|
|
671
|
-
method: 'GET',
|
|
672
|
-
url: `${FORM_PREFIX}/slug/exit`
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
const res = await server.inject(options)
|
|
676
|
-
|
|
677
|
-
expect(res.statusCode).toBe(StatusCodes.OK)
|
|
678
|
-
expect(res.result).toContain('Your progress has been saved')
|
|
679
|
-
})
|
|
680
|
-
})
|
package/src/server/index.ts
CHANGED
|
@@ -82,8 +82,11 @@ export async function createServer(routeConfig?: RouteConfig) {
|
|
|
82
82
|
prepareSecureContext(server)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
const cacheService = routeConfig?.cacheServiceCreator
|
|
86
|
+
? routeConfig.cacheServiceCreator(server)
|
|
87
|
+
: undefined
|
|
85
88
|
const pluginCrumb = configureCrumbPlugin(routeConfig)
|
|
86
|
-
const pluginEngine = await configureEnginePlugin(routeConfig)
|
|
89
|
+
const pluginEngine = await configureEnginePlugin(routeConfig, cacheService)
|
|
87
90
|
|
|
88
91
|
await server.register(pluginSession)
|
|
89
92
|
await server.register(pluginPulse)
|
|
@@ -86,9 +86,9 @@ There are a number of `LiquidJS` filters available to you from within the templa
|
|
|
86
86
|
]
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
### Save and
|
|
89
|
+
### Save and exit
|
|
90
90
|
|
|
91
|
-
See [our save and
|
|
91
|
+
See [our save and exit feature page](/docs/features/code-based/SAVE_AND_EXIT.md).
|
|
92
92
|
|
|
93
93
|
### Additional notes
|
|
94
94
|
|
|
@@ -25,7 +25,7 @@ describe('helpers tests', () => {
|
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
describe('ComponentBase tests', () => {
|
|
28
|
-
test('should handle save and
|
|
28
|
+
test('should handle save and exit functionality', () => {
|
|
29
29
|
const mockComponentDef = {
|
|
30
30
|
type: 'TextField',
|
|
31
31
|
name: 'testField',
|
|
@@ -10,17 +10,21 @@ import { formsService } from '~/src/server/plugins/engine/services/localFormsSer
|
|
|
10
10
|
import { type PluginOptions } from '~/src/server/plugins/engine/types.js'
|
|
11
11
|
import { findPackageRoot } from '~/src/server/plugins/engine/vision.js'
|
|
12
12
|
import { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'
|
|
13
|
+
import { type CacheService } from '~/src/server/services/cacheService.js'
|
|
13
14
|
import { type RouteConfig } from '~/src/server/types.js'
|
|
14
15
|
|
|
15
|
-
export const configureEnginePlugin = async (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
export const configureEnginePlugin = async (
|
|
17
|
+
{
|
|
18
|
+
formFileName,
|
|
19
|
+
formFilePath,
|
|
20
|
+
services,
|
|
21
|
+
controllers,
|
|
22
|
+
preparePageEventRequestOptions,
|
|
23
|
+
onRequest,
|
|
24
|
+
saveAndExit
|
|
25
|
+
}: RouteConfig = {},
|
|
26
|
+
cache?: CacheService
|
|
27
|
+
): Promise<{
|
|
24
28
|
plugin: typeof plugin
|
|
25
29
|
options: PluginOptions
|
|
26
30
|
}> => {
|
|
@@ -50,7 +54,7 @@ export const configureEnginePlugin = async ({
|
|
|
50
54
|
formsService: await formsService()
|
|
51
55
|
},
|
|
52
56
|
controllers,
|
|
53
|
-
|
|
57
|
+
cache: cache ?? 'session',
|
|
54
58
|
nunjucks: {
|
|
55
59
|
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
56
60
|
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
@@ -59,7 +63,7 @@ export const configureEnginePlugin = async ({
|
|
|
59
63
|
preparePageEventRequestOptions,
|
|
60
64
|
onRequest,
|
|
61
65
|
baseUrl: 'http://localhost:3009', // always runs locally
|
|
62
|
-
|
|
66
|
+
saveAndExit
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
}
|
|
@@ -30,7 +30,8 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
FormAction,
|
|
32
32
|
FormStatus,
|
|
33
|
-
type FormRequest
|
|
33
|
+
type FormRequest,
|
|
34
|
+
type FormResponseToolkit
|
|
34
35
|
} from '~/src/server/routes/types.js'
|
|
35
36
|
import definition from '~/test/form/definitions/basic.js'
|
|
36
37
|
import templateDefinition from '~/test/form/definitions/templates.js'
|
|
@@ -47,7 +48,7 @@ type HrefFilter = (this: NunjucksContext, path: string) => string | undefined
|
|
|
47
48
|
describe('Helpers', () => {
|
|
48
49
|
let page: PageControllerClass
|
|
49
50
|
let request: FormContextRequest
|
|
50
|
-
let h:
|
|
51
|
+
let h: FormResponseToolkit
|
|
51
52
|
|
|
52
53
|
beforeEach(() => {
|
|
53
54
|
const model = new FormModel(definition, {
|