@defra/forms-engine-plugin 1.0.3 → 1.0.5
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/plugins/engine/configureEnginePlugin.d.ts +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +6 -2
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/options.js +3 -1
- package/.server/server/plugins/engine/options.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +2 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +1 -1
- package/.server/server/plugins/engine/plugin.js +3 -2
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +7 -1
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.d.ts +2 -1
- package/.server/server/plugins/engine/routes/questions.js +47 -41
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +9 -4
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/schemas/index.d.ts +2 -2
- package/.server/server/schemas/index.js.map +1 -1
- package/.server/server/types.d.ts +3 -0
- package/.server/server/types.js.map +1 -1
- package/package.json +2 -1
- package/src/server/plugins/engine/configureEnginePlugin.ts +6 -2
- package/src/server/plugins/engine/options.js +3 -1
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +2 -2
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +3 -3
- package/src/server/plugins/engine/plugin.ts +7 -2
- package/src/server/plugins/engine/routes/index.ts +6 -1
- package/src/server/plugins/engine/routes/questions.ts +47 -35
- package/src/server/plugins/engine/types.ts +23 -2
- package/src/server/schemas/index.ts +2 -2
- package/src/server/types.ts +6 -0
|
@@ -2,7 +2,7 @@ import { type FormDefinition } from '@defra/forms-model';
|
|
|
2
2
|
import { plugin } from '~/src/server/plugins/engine/plugin.js';
|
|
3
3
|
import { type PluginOptions } from '~/src/server/plugins/engine/types.js';
|
|
4
4
|
import { type RouteConfig } from '~/src/server/types.js';
|
|
5
|
-
export declare const configureEnginePlugin: ({ formFileName, formFilePath, services, controllers }?: RouteConfig) => Promise<{
|
|
5
|
+
export declare const configureEnginePlugin: ({ formFileName, formFilePath, services, controllers, preparePageEventRequestOptions, onRequest }?: RouteConfig) => Promise<{
|
|
6
6
|
plugin: typeof plugin;
|
|
7
7
|
options: PluginOptions;
|
|
8
8
|
}>;
|
|
@@ -10,7 +10,9 @@ export const configureEnginePlugin = async ({
|
|
|
10
10
|
formFileName,
|
|
11
11
|
formFilePath,
|
|
12
12
|
services,
|
|
13
|
-
controllers
|
|
13
|
+
controllers,
|
|
14
|
+
preparePageEventRequestOptions,
|
|
15
|
+
onRequest
|
|
14
16
|
} = {}) => {
|
|
15
17
|
let model;
|
|
16
18
|
if (formFileName && formFilePath) {
|
|
@@ -38,7 +40,9 @@ export const configureEnginePlugin = async ({
|
|
|
38
40
|
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
39
41
|
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
40
42
|
},
|
|
41
|
-
viewContext: devtoolContext
|
|
43
|
+
viewContext: devtoolContext,
|
|
44
|
+
preparePageEventRequestOptions,
|
|
45
|
+
onRequest
|
|
42
46
|
}
|
|
43
47
|
};
|
|
44
48
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configureEnginePlugin.js","names":["join","parse","FORM_PREFIX","FormModel","plugin","defaultServices","formsService","findPackageRoot","devtoolContext","configureEnginePlugin","formFileName","formFilePath","services","controllers","model","definition","getForm","name","initialBasePath","basePath","options","cacheName","nunjucks","baseLayoutPath","paths","viewContext","importPath","ext","attributes","type","formImport","with","default"],"sources":["../../../../src/server/plugins/engine/configureEnginePlugin.ts"],"sourcesContent":["import { join, parse } from 'node:path'\n\nimport { type FormDefinition } from '@defra/forms-model'\n\nimport { FORM_PREFIX } from '~/src/server/constants.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { findPackageRoot } from '~/src/server/plugins/engine/vision.js'\nimport { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nexport const configureEnginePlugin = async ({\n formFileName,\n formFilePath,\n services,\n controllers\n}: RouteConfig = {}): Promise<{\n plugin: typeof plugin\n options: PluginOptions\n}> => {\n let model: FormModel | undefined\n\n if (formFileName && formFilePath) {\n const definition = await getForm(join(formFilePath, formFileName))\n const { name } = parse(formFileName)\n\n const initialBasePath = `${FORM_PREFIX}${name}`\n\n model = new FormModel(\n definition,\n { basePath: initialBasePath },\n services,\n controllers\n )\n }\n\n return {\n plugin,\n options: {\n model,\n services: services ?? {\n // services for testing, else use the disk loader option for running this service locally\n ...defaultServices,\n formsService: await formsService()\n },\n controllers,\n cacheName: 'session',\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: devtoolContext\n }\n }\n}\n\nexport async function getForm(importPath: string) {\n const { ext } = parse(importPath)\n\n const attributes: ImportAttributes = {\n type: ext === '.json' ? 'json' : 'module'\n }\n\n const formImport = import(importPath, { with: attributes }) as Promise<{\n default: FormDefinition\n }>\n\n const { default: definition } = await formImport\n return definition\n}\n"],"mappings":"AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,WAAW;AAIvC,SAASC,WAAW;AACpB,SAASC,SAAS;AAClB,SAASC,MAAM;AACf,OAAO,KAAKC,eAAe;AAC3B,SAASC,YAAY;AAErB,SAASC,eAAe;AACxB,SAASC,cAAc;AAGvB,OAAO,MAAMC,qBAAqB,GAAG,MAAAA,CAAO;EAC1CC,YAAY;EACZC,YAAY;EACZC,QAAQ;EACRC;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,
|
|
1
|
+
{"version":3,"file":"configureEnginePlugin.js","names":["join","parse","FORM_PREFIX","FormModel","plugin","defaultServices","formsService","findPackageRoot","devtoolContext","configureEnginePlugin","formFileName","formFilePath","services","controllers","preparePageEventRequestOptions","onRequest","model","definition","getForm","name","initialBasePath","basePath","options","cacheName","nunjucks","baseLayoutPath","paths","viewContext","importPath","ext","attributes","type","formImport","with","default"],"sources":["../../../../src/server/plugins/engine/configureEnginePlugin.ts"],"sourcesContent":["import { join, parse } from 'node:path'\n\nimport { type FormDefinition } from '@defra/forms-model'\n\nimport { FORM_PREFIX } from '~/src/server/constants.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { findPackageRoot } from '~/src/server/plugins/engine/vision.js'\nimport { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nexport const configureEnginePlugin = async ({\n formFileName,\n formFilePath,\n services,\n controllers,\n preparePageEventRequestOptions,\n onRequest\n}: RouteConfig = {}): Promise<{\n plugin: typeof plugin\n options: PluginOptions\n}> => {\n let model: FormModel | undefined\n\n if (formFileName && formFilePath) {\n const definition = await getForm(join(formFilePath, formFileName))\n const { name } = parse(formFileName)\n\n const initialBasePath = `${FORM_PREFIX}${name}`\n\n model = new FormModel(\n definition,\n { basePath: initialBasePath },\n services,\n controllers\n )\n }\n\n return {\n plugin,\n options: {\n model,\n services: services ?? {\n // services for testing, else use the disk loader option for running this service locally\n ...defaultServices,\n formsService: await formsService()\n },\n controllers,\n cacheName: 'session',\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: devtoolContext,\n preparePageEventRequestOptions,\n onRequest\n }\n }\n}\n\nexport async function getForm(importPath: string) {\n const { ext } = parse(importPath)\n\n const attributes: ImportAttributes = {\n type: ext === '.json' ? 'json' : 'module'\n }\n\n const formImport = import(importPath, { with: attributes }) as Promise<{\n default: FormDefinition\n }>\n\n const { default: definition } = await formImport\n return definition\n}\n"],"mappings":"AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,WAAW;AAIvC,SAASC,WAAW;AACpB,SAASC,SAAS;AAClB,SAASC,MAAM;AACf,OAAO,KAAKC,eAAe;AAC3B,SAASC,YAAY;AAErB,SAASC,eAAe;AACxB,SAASC,cAAc;AAGvB,OAAO,MAAMC,qBAAqB,GAAG,MAAAA,CAAO;EAC1CC,YAAY;EACZC,YAAY;EACZC,QAAQ;EACRC,WAAW;EACXC,8BAA8B;EAC9BC;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,IAAIN,YAAY,IAAIC,YAAY,EAAE;IAChC,MAAMM,UAAU,GAAG,MAAMC,OAAO,CAAClB,IAAI,CAACW,YAAY,EAAED,YAAY,CAAC,CAAC;IAClE,MAAM;MAAES;IAAK,CAAC,GAAGlB,KAAK,CAACS,YAAY,CAAC;IAEpC,MAAMU,eAAe,GAAG,GAAGlB,WAAW,GAAGiB,IAAI,EAAE;IAE/CH,KAAK,GAAG,IAAIb,SAAS,CACnBc,UAAU,EACV;MAAEI,QAAQ,EAAED;IAAgB,CAAC,EAC7BR,QAAQ,EACRC,WACF,CAAC;EACH;EAEA,OAAO;IACLT,MAAM;IACNkB,OAAO,EAAE;MACPN,KAAK;MACLJ,QAAQ,EAAEA,QAAQ,IAAI;QACpB;QACA,GAAGP,eAAe;QAClBC,YAAY,EAAE,MAAMA,YAAY,CAAC;MACnC,CAAC;MACDO,WAAW;MACXU,SAAS,EAAE,SAAS;MACpBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC1B,IAAI,CAACO,eAAe,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;MAC3D,CAAC;MACDoB,WAAW,EAAEnB,cAAc;MAC3BM,8BAA8B;MAC9BC;IACF;EACF,CAAC;AACH,CAAC;AAED,OAAO,eAAeG,OAAOA,CAACU,UAAkB,EAAE;EAChD,MAAM;IAAEC;EAAI,CAAC,GAAG5B,KAAK,CAAC2B,UAAU,CAAC;EAEjC,MAAME,UAA4B,GAAG;IACnCC,IAAI,EAAEF,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG;EACnC,CAAC;EAED,MAAMG,UAAU,GAAG,MAAM,CAACJ,UAAU,EAAE;IAAEK,IAAI,EAAEH;EAAW,CAAC,CAExD;EAEF,MAAM;IAAEI,OAAO,EAAEjB;EAAW,CAAC,GAAG,MAAMe,UAAU;EAChD,OAAOf,UAAU;AACnB","ignoreList":[]}
|
|
@@ -10,7 +10,9 @@ const pluginRegistrationOptionsSchema = Joi.object({
|
|
|
10
10
|
baseLayoutPath: Joi.string().required(),
|
|
11
11
|
paths: Joi.array().items(Joi.string()).required()
|
|
12
12
|
}).required(),
|
|
13
|
-
viewContext: Joi.function().required()
|
|
13
|
+
viewContext: Joi.function().required(),
|
|
14
|
+
preparePageEventRequestOptions: Joi.function().optional(),
|
|
15
|
+
onRequest: Joi.function().optional()
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","names":["Joi","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","validatePluginOptions","options","result","validate","abortEarly","error","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\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 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})\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 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,MAAMC,+BAA+B,GAAGD,GAAG,CAACE,MAAM,CAAC;EACjDC,KAAK,EAAEH,GAAG,CAACE,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEL,GAAG,CAACE,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAEN,GAAG,CAACE,MAAM,CAAC,CAAC,CAACK,OAAO,CAACP,GAAG,CAACQ,MAAM,CAAC,CAAC,EAAER,GAAG,CAACS,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEV,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEX,GAAG,CAACE,MAAM,CAAC,CAAC,CAACK,OAAO,CAACP,GAAG,CAACQ,MAAM,CAAC,CAAC,EAAER,GAAG,CAACS,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,UAAU,EAAEZ,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCS,QAAQ,EAAEb,GAAG,CAACE,MAAM,CAAC;IACnBY,cAAc,EAAEd,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEhB,GAAG,CAACiB,KAAK,CAAC,CAAC,CAACC,KAAK,CAAClB,GAAG,CAACQ,MAAM,CAAC,CAAC,CAAC,CAACO,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAEnB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"options.js","names":["Joi","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","validatePluginOptions","options","result","validate","abortEarly","error","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\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 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})\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 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,MAAMC,+BAA+B,GAAGD,GAAG,CAACE,MAAM,CAAC;EACjDC,KAAK,EAAEH,GAAG,CAACE,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEL,GAAG,CAACE,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAEN,GAAG,CAACE,MAAM,CAAC,CAAC,CAACK,OAAO,CAACP,GAAG,CAACQ,MAAM,CAAC,CAAC,EAAER,GAAG,CAACS,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEV,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEX,GAAG,CAACE,MAAM,CAAC,CAAC,CAACK,OAAO,CAACP,GAAG,CAACQ,MAAM,CAAC,CAAC,EAAER,GAAG,CAACS,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,UAAU,EAAEZ,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCS,QAAQ,EAAEb,GAAG,CAACE,MAAM,CAAC;IACnBY,cAAc,EAAEd,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEhB,GAAG,CAACiB,KAAK,CAAC,CAAC,CAACC,KAAK,CAAClB,GAAG,CAACQ,MAAM,CAAC,CAAC,CAAC,CAACO,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAEnB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAErB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAAChB,QAAQ,CAAC,CAAC;EACzDkB,SAAS,EAAEtB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAAChB,QAAQ,CAAC;AACrC,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASmB,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAGxB,+BAA+B,CAACyB,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChB,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEJ,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACK,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
|
|
@@ -5,7 +5,7 @@ import { ComponentCollection } from '~/src/server/plugins/engine/components/Comp
|
|
|
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
|
|
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
9
|
import { type FormRequest, type FormRequestPayload, type FormRequestPayloadRefs, type FormRequestRefs } from '~/src/server/routes/types.js';
|
|
10
10
|
export declare class QuestionPageController extends PageController {
|
|
11
11
|
collection: ComponentCollection;
|
|
@@ -32,7 +32,7 @@ export declare class QuestionPageController extends PageController {
|
|
|
32
32
|
/**
|
|
33
33
|
* Gets form params (from payload) for this page only
|
|
34
34
|
*/
|
|
35
|
-
getFormParams(request?: FormContextRequest):
|
|
35
|
+
getFormParams(request?: FormContextRequest): FormPayloadParams;
|
|
36
36
|
getStateFromValidForm(request: FormContextRequest, state: FormSubmissionState, payload: FormPayload): FormState;
|
|
37
37
|
getErrors(details?: ValidationErrorItem[]): import("~/src/server/plugins/engine/types.js").FormSubmissionError[] | undefined;
|
|
38
38
|
getState(request: FormRequest | FormRequestPayload): Promise<FormSubmissionState>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","ComponentCollection","optionalText","getCacheService","getErrors","normalisePath","proceed","PageController","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","backLink","getBackLink","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","server","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","getViewErrors","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","nextPath","nextUrl","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n getCacheService,\n getErrors,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormParams,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\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'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors\n }\n }\n\n getRelevantPath(\n request: FormRequest | FormRequestPayload,\n context: FormContext\n ) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save and proceed\n await this.setState(request, state)\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n proceed(\n request: FormContextRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","ComponentCollection","optionalText","getCacheService","getErrors","normalisePath","proceed","PageController","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","backLink","getBackLink","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","server","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","getViewErrors","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","nextPath","nextUrl","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n getCacheService,\n getErrors,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormPayloadParams,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\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'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors\n }\n }\n\n getRelevantPath(\n request: FormRequest | FormRequestPayload,\n context: FormContext\n ) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormPayloadParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormPayloadParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save and proceed\n await this.setState(request, state)\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n proceed(\n request: FormContextRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -11,7 +11,7 @@ export declare class RepeatPageController extends QuestionPageController {
|
|
|
11
11
|
repeat: Repeat;
|
|
12
12
|
constructor(model: FormModel, pageDef: PageRepeat);
|
|
13
13
|
get keys(): string[];
|
|
14
|
-
getFormParams(request?: FormContextRequest): import("~/src/server/plugins/engine/types.js").
|
|
14
|
+
getFormParams(request?: FormContextRequest): import("~/src/server/plugins/engine/types.js").FormPayloadParams;
|
|
15
15
|
getFormDataFromState(request: FormContextRequest | undefined, state: FormSubmissionState): {
|
|
16
16
|
action?: FormAction;
|
|
17
17
|
confirm?: true;
|
|
@@ -18,7 +18,8 @@ export const plugin = {
|
|
|
18
18
|
keyGenerator,
|
|
19
19
|
sessionHydrator,
|
|
20
20
|
nunjucks: nunjucksOptions,
|
|
21
|
-
viewContext
|
|
21
|
+
viewContext,
|
|
22
|
+
preparePageEventRequestOptions
|
|
22
23
|
} = options;
|
|
23
24
|
const cacheService = new CacheService({
|
|
24
25
|
server,
|
|
@@ -52,7 +53,7 @@ export const plugin = {
|
|
|
52
53
|
method: loadFormPreHandler
|
|
53
54
|
}]
|
|
54
55
|
};
|
|
55
|
-
const routes = [...getQuestionRoutes(getRouteOptions, postRouteOptions), ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions), ...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions), ...getFileUploadStatusRoutes()];
|
|
56
|
+
const routes = [...getQuestionRoutes(getRouteOptions, postRouteOptions, preparePageEventRequestOptions), ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions), ...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions), ...getFileUploadStatusRoutes()];
|
|
56
57
|
server.route(routes); // TODO
|
|
57
58
|
}
|
|
58
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cacheName","keyGenerator","sessionHydrator","nunjucks","nunjucksOptions","viewContext","cacheService","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cacheName,\n keyGenerator,\n sessionHydrator,\n nunjucks: nunjucksOptions,\n viewContext\n } = options\n const cacheService = new CacheService({\n server,\n cacheName,\n options: {\n keyGenerator,\n sessionHydrator\n }\n })\n\n await registerVision(server, options)\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...getQuestionRoutes(getRouteOptions
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cacheName","keyGenerator","sessionHydrator","nunjucks","nunjucksOptions","viewContext","preparePageEventRequestOptions","cacheService","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cacheName,\n keyGenerator,\n sessionHydrator,\n nunjucks: nunjucksOptions,\n viewContext,\n preparePageEventRequestOptions\n } = options\n const cacheService = new CacheService({\n server,\n cacheName,\n options: {\n keyGenerator,\n sessionHydrator\n }\n })\n\n await registerVision(server, options)\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...getQuestionRoutes(\n getRouteOptions,\n postRouteOptions,\n preparePageEventRequestOptions\n ),\n ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions),\n ...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions),\n ...getFileUploadStatusRoutes()\n ]\n\n server.route(routes as unknown as ServerRoute[]) // TODO\n }\n} satisfies Plugin<PluginOptions>\n"],"mappings":"AASA,SAASA,qBAAqB;AAC9B,SAASC,SAAS,IAAIC,yBAAyB;AAC/C,SAASC,sBAAsB;AAC/B,SAASF,SAAS,IAAIG,iBAAiB;AACvC,SAASH,SAAS,IAAII,2BAA2B;AACjD,SAASJ,SAAS,IAAIK,wBAAwB;AAE9C,SAASC,cAAc;AAKvB,SAASC,YAAY;AAErB,OAAO,MAAMC,MAAM,GAAG;EACpBC,IAAI,EAAE,4BAA4B;EAClCC,YAAY,EAAE,CAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC;EACvDC,QAAQ,EAAE,IAAI;EACd,MAAMC,QAAQA,CAACC,MAAc,EAAEC,OAAsB,EAAE;IACrDA,OAAO,GAAGf,qBAAqB,CAACe,OAAO,CAAC;IAExC,MAAM;MACJC,KAAK;MACLC,SAAS;MACTC,YAAY;MACZC,eAAe;MACfC,QAAQ,EAAEC,eAAe;MACzBC,WAAW;MACXC;IACF,CAAC,GAAGR,OAAO;IACX,MAAMS,YAAY,GAAG,IAAIhB,YAAY,CAAC;MACpCM,MAAM;MACNG,SAAS;MACTF,OAAO,EAAE;QACPG,YAAY;QACZC;MACF;IACF,CAAC,CAAC;IAEF,MAAMZ,cAAc,CAACO,MAAM,EAAEC,OAAO,CAAC;IAErCD,MAAM,CAACW,MAAM,CAAC,gBAAgB,EAAEJ,eAAe,CAACK,cAAc,CAAC;IAC/DZ,MAAM,CAACW,MAAM,CAAC,aAAa,EAAEH,WAAW,CAAC;IACzCR,MAAM,CAACW,MAAM,CAAC,cAAc,EAAED,YAAY,CAAC;IAE3CV,MAAM,CAACa,GAAG,CAACX,KAAK,GAAGA,KAAK;;IAExB;IACA;IACA,MAAMY,SAAS,GAAG,IAAIC,GAAG,CAAgD,CAAC;IAC1Ef,MAAM,CAACa,GAAG,CAACG,MAAM,GAAGF,SAAS;IAE7B,MAAMG,kBAAkB,GAAG5B,sBAAsB,CAACW,MAAM,EAAEC,OAAO,CAAC;IAElE,MAAMiB,eAA8C,GAAG;MACrDC,GAAG,EAAE,CACH;QACEC,MAAM,EACJH;MACJ,CAAC;IAEL,CAAC;IAED,MAAMI,gBAAsD,GAAG;MAC7DC,OAAO,EAAE;QACPC,KAAK,EAAE;MACT,CAAC;MACDJ,GAAG,EAAE,CACH;QACEC,MAAM,EACJH;MACJ,CAAC;IAEL,CAAC;IAED,MAAMO,MAAM,GAAG,CACb,GAAGlC,iBAAiB,CAClB4B,eAAe,EACfG,gBAAgB,EAChBZ,8BACF,CAAC,EACD,GAAGjB,wBAAwB,CAAC0B,eAAe,EAAEG,gBAAgB,CAAC,EAC9D,GAAG9B,2BAA2B,CAAC2B,eAAe,EAAEG,gBAAgB,CAAC,EACjE,GAAGjC,yBAAyB,CAAC,CAAC,CAC/B;IAEDY,MAAM,CAACyB,KAAK,CAACD,MAAkC,CAAC,EAAC;EACnD;AACF,CAAiC","ignoreList":[]}
|
|
@@ -53,7 +53,8 @@ export function makeLoadFormPreHandler(server, options) {
|
|
|
53
53
|
const prefix = server.realm.modifiers.route.prefix ?? '';
|
|
54
54
|
const {
|
|
55
55
|
services = defaultServices,
|
|
56
|
-
controllers
|
|
56
|
+
controllers,
|
|
57
|
+
onRequest
|
|
57
58
|
} = options;
|
|
58
59
|
const {
|
|
59
60
|
formsService
|
|
@@ -124,6 +125,11 @@ export function makeLoadFormPreHandler(server, options) {
|
|
|
124
125
|
server.app.models.set(key, item);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
// Call the onRequest callback if it has been supplied
|
|
129
|
+
if (onRequest) {
|
|
130
|
+
onRequest(request, params, item.model.def, metadata);
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
// Assign the model to the request data
|
|
128
134
|
// for use in the downstream handler
|
|
129
135
|
request.app.model = item.model;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Boom","isEqual","PREVIEW_PATH_PREFIX","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","FormModel","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","formsService","handler","continue","slug","isPreview","formState","getFormMetadata","id","key","item","models","get","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","set","dispatchHandler","servicePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\nimport { isEqual } from 'date-fns'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: FormRequest | FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const { services = defaultServices, controllers } = options\n\n const { formsService } = services\n\n async function handler(\n request: FormRequest | FormRequestPayload,\n h: ResponseToolkit\n ) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n // Get the form metadata using the `slug` param\n const metadata = await formsService.getFormMetadata(slug)\n\n const { id, [formState]: state } = metadata\n\n // Check the metadata supports the requested state\n if (!state) {\n throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)\n }\n\n // Cache the models based on id, state and whether\n // it's a preview or not. There could be up to 3 models\n // cached for a single form:\n // \"{id}_live_false\" (live/live)\n // \"{id}_live_true\" (live/preview)\n // \"{id}_draft_true\" (draft/preview)\n const key = `${id}_${formState}_${isPreview}`\n let item = server.app.models.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)\n\n // Get the form definition using the `id` from the metadata\n const definition = await formsService.getFormDefinition(id, formState)\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${id} (${slug}) ${formState}`\n )\n }\n\n const emailAddress = metadata.notificationEmail ?? definition.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Build the form model\n server.logger.info(\n `Building model for form definition ${id} (${slug}) ${formState}`\n )\n\n // Set up the basePath for the model\n const basePath = (\n isPreview\n ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`\n : `${prefix}/${slug}`\n ).substring(1)\n\n // Construct the form model\n const model = new FormModel(\n definition,\n { basePath },\n services,\n controllers\n )\n\n // Create new item and add it to the item cache\n item = { model, updatedAt: state.updatedAt }\n server.app.models.set(key, item)\n }\n\n // Assign the model to the request data\n // for use in the downstream handler\n request.app.model = item.model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Boom","isEqual","PREVIEW_PATH_PREFIX","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","FormModel","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","onRequest","formsService","handler","continue","slug","isPreview","formState","getFormMetadata","id","key","item","models","get","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","set","dispatchHandler","servicePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\nimport { isEqual } from 'date-fns'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: FormRequest | FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const { services = defaultServices, controllers, onRequest } = options\n\n const { formsService } = services\n\n async function handler(\n request: FormRequest | FormRequestPayload,\n h: ResponseToolkit\n ) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n // Get the form metadata using the `slug` param\n const metadata = await formsService.getFormMetadata(slug)\n\n const { id, [formState]: state } = metadata\n\n // Check the metadata supports the requested state\n if (!state) {\n throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)\n }\n\n // Cache the models based on id, state and whether\n // it's a preview or not. There could be up to 3 models\n // cached for a single form:\n // \"{id}_live_false\" (live/live)\n // \"{id}_live_true\" (live/preview)\n // \"{id}_draft_true\" (draft/preview)\n const key = `${id}_${formState}_${isPreview}`\n let item = server.app.models.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)\n\n // Get the form definition using the `id` from the metadata\n const definition = await formsService.getFormDefinition(id, formState)\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${id} (${slug}) ${formState}`\n )\n }\n\n const emailAddress = metadata.notificationEmail ?? definition.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Build the form model\n server.logger.info(\n `Building model for form definition ${id} (${slug}) ${formState}`\n )\n\n // Set up the basePath for the model\n const basePath = (\n isPreview\n ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`\n : `${prefix}/${slug}`\n ).substring(1)\n\n // Construct the form model\n const model = new FormModel(\n definition,\n { basePath },\n services,\n controllers\n )\n\n // Create new item and add it to the item cache\n item = { model, updatedAt: state.updatedAt }\n server.app.models.set(key, item)\n }\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n onRequest(request, params, item.model.def, metadata)\n }\n\n // Assign the model to the request data\n // for use in the downstream handler\n request.app.model = item.model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type RouteOptions, type ServerRoute } from '@hapi/hapi';
|
|
2
|
+
import { type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
|
|
2
3
|
import { type FormRequestPayloadRefs, type FormRequestRefs } from '~/src/server/routes/types.js';
|
|
3
|
-
export declare function getRoutes(getRouteOptions: RouteOptions<FormRequestRefs>, postRouteOptions: RouteOptions<FormRequestPayloadRefs
|
|
4
|
+
export declare function getRoutes(getRouteOptions: RouteOptions<FormRequestRefs>, postRouteOptions: RouteOptions<FormRequestPayloadRefs>, preparePageEventRequestOptions?: PreparePageEventRequestOptions): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[];
|
|
@@ -8,49 +8,55 @@ import { getFormSubmissionData } from "../pageControllers/SummaryPageController.
|
|
|
8
8
|
import { dispatchHandler, redirectOrMakeHandler } from "./index.js";
|
|
9
9
|
import { actionSchema, crumbSchema, itemIdSchema, pathSchema, stateSchema } from "../../../schemas/index.js";
|
|
10
10
|
import * as httpService from "../../../services/httpService.js";
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
params
|
|
14
|
-
} = request;
|
|
15
|
-
if (normalisePath(params.path) === '') {
|
|
16
|
-
return dispatchHandler(request, h);
|
|
17
|
-
}
|
|
18
|
-
return redirectOrMakeHandler(request, h, async (page, context) => {
|
|
19
|
-
// Check for a page onLoad HTTP event and if one exists,
|
|
20
|
-
// call it and assign the response to the context data
|
|
11
|
+
function makeGetHandler(preparePageEventRequestOptions) {
|
|
12
|
+
return function getHandler(request, h) {
|
|
21
13
|
const {
|
|
22
|
-
|
|
23
|
-
} =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} = request.app;
|
|
27
|
-
if (!model) {
|
|
28
|
-
throw Boom.notFound(`No model found for /${params.path}`);
|
|
14
|
+
params
|
|
15
|
+
} = request;
|
|
16
|
+
if (normalisePath(params.path) === '') {
|
|
17
|
+
return dispatchHandler(request, h);
|
|
29
18
|
}
|
|
30
|
-
|
|
19
|
+
return redirectOrMakeHandler(request, h, async (page, context) => {
|
|
20
|
+
// Check for a page onLoad HTTP event and if one exists,
|
|
21
|
+
// call it and assign the response to the context data
|
|
31
22
|
const {
|
|
32
|
-
|
|
33
|
-
} =
|
|
23
|
+
events
|
|
24
|
+
} = page;
|
|
34
25
|
const {
|
|
35
|
-
|
|
36
|
-
} =
|
|
26
|
+
model
|
|
27
|
+
} = request.app;
|
|
28
|
+
if (!model) {
|
|
29
|
+
throw Boom.notFound(`No model found for /${params.path}`);
|
|
30
|
+
}
|
|
31
|
+
if (events?.onLoad && events.onLoad.type === 'http') {
|
|
32
|
+
const {
|
|
33
|
+
options
|
|
34
|
+
} = events.onLoad;
|
|
35
|
+
const {
|
|
36
|
+
url
|
|
37
|
+
} = options;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// TODO: Update structured data POST payload with when helper
|
|
40
|
+
// is updated to removing the dependency on `SummaryViewModel` etc.
|
|
41
|
+
const viewModel = new SummaryViewModel(request, page, context);
|
|
42
|
+
const items = getFormSubmissionData(viewModel.context, viewModel.details);
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
45
|
+
const payload = format(items, model, undefined, undefined);
|
|
46
|
+
const opts = {
|
|
47
|
+
payload
|
|
48
|
+
};
|
|
49
|
+
if (preparePageEventRequestOptions) {
|
|
50
|
+
preparePageEventRequestOptions(opts, events.onLoad, page, context);
|
|
51
|
+
}
|
|
52
|
+
const {
|
|
53
|
+
payload: response
|
|
54
|
+
} = await httpService.postJson(url, opts);
|
|
55
|
+
Object.assign(context.data, response);
|
|
56
|
+
}
|
|
57
|
+
return page.makeGetRouteHandler()(request, context, h);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
function postHandler(request, h) {
|
|
56
62
|
const {
|
|
@@ -71,11 +77,11 @@ function postHandler(request, h) {
|
|
|
71
77
|
return page.makePostRouteHandler()(request, context, h);
|
|
72
78
|
});
|
|
73
79
|
}
|
|
74
|
-
export function getRoutes(getRouteOptions, postRouteOptions) {
|
|
80
|
+
export function getRoutes(getRouteOptions, postRouteOptions, preparePageEventRequestOptions) {
|
|
75
81
|
return [{
|
|
76
82
|
method: 'get',
|
|
77
83
|
path: '/{slug}',
|
|
78
|
-
handler:
|
|
84
|
+
handler: makeGetHandler(preparePageEventRequestOptions),
|
|
79
85
|
options: {
|
|
80
86
|
...getRouteOptions,
|
|
81
87
|
validate: {
|
|
@@ -100,7 +106,7 @@ export function getRoutes(getRouteOptions, postRouteOptions) {
|
|
|
100
106
|
}, {
|
|
101
107
|
method: 'get',
|
|
102
108
|
path: '/{slug}/{path}/{itemId?}',
|
|
103
|
-
handler:
|
|
109
|
+
handler: makeGetHandler(preparePageEventRequestOptions),
|
|
104
110
|
options: {
|
|
105
111
|
...getRouteOptions,
|
|
106
112
|
validate: {
|
|
@@ -114,7 +120,7 @@ export function getRoutes(getRouteOptions, postRouteOptions) {
|
|
|
114
120
|
}, {
|
|
115
121
|
method: 'get',
|
|
116
122
|
path: '/preview/{state}/{slug}/{path}/{itemId?}',
|
|
117
|
-
handler:
|
|
123
|
+
handler: makeGetHandler(preparePageEventRequestOptions),
|
|
118
124
|
options: {
|
|
119
125
|
...getRouteOptions,
|
|
120
126
|
validate: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","getHandler","request","h","params","path","page","context","events","model","app","notFound","onLoad","type","options","url","viewModel","items","details","payload","undefined","response","postJson","Object","assign","data","makeGetRouteHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseToolkit,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { SummaryViewModel } from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nfunction getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(request, h, async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad && events.onLoad.type === 'http') {\n const { options } = events.onLoad\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(items, model, undefined, undefined)\n\n const { payload: response } = await httpService.postJson(url, {\n payload\n })\n\n Object.assign(context.data, response)\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n })\n}\n\nfunction postHandler(\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { query } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n return page.makePostRouteHandler()(request, context, h)\n })\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: getHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: getHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: getHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: postHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: postHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","makeGetHandler","preparePageEventRequestOptions","getHandler","request","h","params","path","page","context","events","model","app","notFound","onLoad","type","options","url","viewModel","items","details","payload","undefined","opts","response","postJson","Object","assign","data","makeGetRouteHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseToolkit,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { SummaryViewModel } from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport { type PreparePageEventRequestOptions } 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'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nfunction makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n return function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(request, h, async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad && events.onLoad.type === 'http') {\n const { options } = events.onLoad\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(\n viewModel.context,\n viewModel.details\n )\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(items, model, undefined, undefined)\n const opts = { payload }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, events.onLoad, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n })\n }\n}\n\nfunction postHandler(\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n) {\n const { query } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n return page.makePostRouteHandler()(request, context, h)\n })\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: postHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: postHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentDef, type Item, type List, type Page } from '@defra/forms-model';
|
|
1
|
+
import { type ComponentDef, type Event, type FormDefinition, type FormMetadata, type Item, type List, type Page } from '@defra/forms-model';
|
|
2
2
|
import { type PluginProperties, type Request } from '@hapi/hapi';
|
|
3
3
|
import { type JoiExpression, type ValidationErrorItem } from 'joi';
|
|
4
4
|
import { type Component } from '~/src/server/plugins/engine/components/helpers.js';
|
|
@@ -7,7 +7,8 @@ import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
|
7
7
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
|
|
8
8
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js';
|
|
9
9
|
import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
10
|
-
import { type FormAction, type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
|
|
10
|
+
import { type FormAction, type FormParams, type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
|
|
11
|
+
import { type RequestOptions } from '~/src/server/services/httpService.js';
|
|
11
12
|
import { type Services } from '~/src/server/types.js';
|
|
12
13
|
/**
|
|
13
14
|
* Form submission state stores the following in Redis:
|
|
@@ -50,7 +51,7 @@ export interface FormSubmissionError extends Pick<ValidationErrorItem, 'context'
|
|
|
50
51
|
name: string;
|
|
51
52
|
text: string;
|
|
52
53
|
}
|
|
53
|
-
export interface
|
|
54
|
+
export interface FormPayloadParams {
|
|
54
55
|
action?: FormAction;
|
|
55
56
|
confirm?: true;
|
|
56
57
|
crumb?: string;
|
|
@@ -60,7 +61,7 @@ export interface FormParams {
|
|
|
60
61
|
* Form POST for question pages
|
|
61
62
|
* (after Joi has converted value types)
|
|
62
63
|
*/
|
|
63
|
-
export type FormPayload =
|
|
64
|
+
export type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>;
|
|
64
65
|
export type FormValue = Item['value'] | Item['value'][] | UploadState | RepeatListState | undefined;
|
|
65
66
|
export type FormState = Partial<Record<string, FormStateValue>>;
|
|
66
67
|
export type FormStateValue = Exclude<FormValue, undefined> | null;
|
|
@@ -257,6 +258,8 @@ export interface ErrorMessageTemplateList {
|
|
|
257
258
|
baseErrors: ErrorMessageTemplate[];
|
|
258
259
|
advancedSettingsErrors: ErrorMessageTemplate[];
|
|
259
260
|
}
|
|
261
|
+
export type PreparePageEventRequestOptions = (options: RequestOptions, event: Event, page: PageControllerClass, context: FormContext) => void;
|
|
262
|
+
export type OnRequestCallback = (request: FormRequest | FormRequestPayload, params: FormParams, definition: FormDefinition, metadata: FormMetadata) => void;
|
|
260
263
|
export interface PluginOptions {
|
|
261
264
|
model?: FormModel;
|
|
262
265
|
services?: Services;
|
|
@@ -271,4 +274,6 @@ export interface PluginOptions {
|
|
|
271
274
|
paths: string[];
|
|
272
275
|
};
|
|
273
276
|
viewContext: PluginProperties['forms-engine-plugin']['viewContext'];
|
|
277
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions;
|
|
278
|
+
onRequest?: OnRequestCallback;
|
|
274
279
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type Services } from '~/src/server/types.js'\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface
|
|
1
|
+
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<FormRequest, 'app' | 'method' | 'params' | 'path' | 'query' | 'url'>\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n filters?: Record<string, FilterFunction>\n keyGenerator?: (request: Request | FormRequest | FormRequestPayload) => string\n sessionHydrator?: (\n request: Request | FormRequest | FormRequestPayload\n ) => Promise<FormSubmissionState>\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n}\n"],"mappings":"AAgCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAkGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Joi from 'joi';
|
|
2
|
-
import { type
|
|
2
|
+
import { type FormPayloadParams } from '~/src/server/plugins/engine/types.js';
|
|
3
3
|
import { FormAction, FormStatus } from '~/src/server/routes/types.js';
|
|
4
4
|
export declare const stateSchema: Joi.StringSchema<FormStatus>;
|
|
5
5
|
export declare const actionSchema: Joi.StringSchema<FormAction>;
|
|
@@ -7,4 +7,4 @@ export declare const pathSchema: Joi.StringSchema<string>;
|
|
|
7
7
|
export declare const itemIdSchema: Joi.StringSchema<string>;
|
|
8
8
|
export declare const crumbSchema: Joi.StringSchema<string>;
|
|
9
9
|
export declare const confirmSchema: Joi.BooleanSchema<boolean>;
|
|
10
|
-
export declare const paramsSchema: Joi.ObjectSchema<
|
|
10
|
+
export declare const paramsSchema: Joi.ObjectSchema<FormPayloadParams>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","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
|
|
1
|
+
{"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","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 )\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,IACb,CAAC,CACAC,OAAO,CAACd,UAAU,CAACU,QAAQ,CAAC,CAC5BK,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,UAAU,GAAGjB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACI,QAAQ,CAAC,CAAC;AACjD,OAAO,MAAMU,YAAY,GAAGlB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACe,IAAI,CAAC,CAAC,CAACX,QAAQ,CAAC,CAAC;AAC1D,OAAO,MAAMY,WAAW,GAAGpB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACY,QAAQ,CAAC,CAAC,CAACK,KAAK,CAAC,EAAE,CAAC;AAC5D,OAAO,MAAMC,aAAa,GAAGtB,GAAG,CAACuB,OAAO,CAAC,CAAC,CAACC,KAAK,CAAC,KAAK,CAAC;AAEvD,OAAO,MAAMC,YAAY,GAAGzB,GAAG,CAAC0B,MAAM,CAAoB,CAAC,CACxDC,IAAI,CAAC;EACJC,MAAM,EAAEnB,YAAY;EACpBoB,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":[]}
|
|
@@ -2,6 +2,7 @@ import { type FormDefinition, type FormMetadata, type SubmitPayload, type Submit
|
|
|
2
2
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
3
3
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
|
|
4
4
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
|
|
5
|
+
import { type OnRequestCallback, type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
|
|
5
6
|
import { type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
|
|
6
7
|
export interface FormsService {
|
|
7
8
|
getFormMetadata: (slug: string) => Promise<FormMetadata>;
|
|
@@ -25,6 +26,8 @@ export interface RouteConfig {
|
|
|
25
26
|
enforceCsrf?: boolean;
|
|
26
27
|
services?: Services;
|
|
27
28
|
controllers?: Record<string, typeof PageController>;
|
|
29
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions;
|
|
30
|
+
onRequest?: OnRequestCallback;
|
|
28
31
|
}
|
|
29
32
|
export interface OutputService {
|
|
30
33
|
submit: (request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload) => 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 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}\n\nexport interface OutputService {\n submit: (\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
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 OnRequestCallback,\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}\n\nexport interface OutputService {\n submit: (\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
|
115
115
|
"@babel/preset-env": "^7.28.0",
|
|
116
116
|
"@babel/preset-typescript": "^7.27.1",
|
|
117
|
+
"@hapi/basic": "^7.0.2",
|
|
117
118
|
"@testing-library/dom": "^10.4.0",
|
|
118
119
|
"@testing-library/jest-dom": "^6.6.3",
|
|
119
120
|
"@types/atob": "^2.1.4",
|
|
@@ -16,7 +16,9 @@ export const configureEnginePlugin = async ({
|
|
|
16
16
|
formFileName,
|
|
17
17
|
formFilePath,
|
|
18
18
|
services,
|
|
19
|
-
controllers
|
|
19
|
+
controllers,
|
|
20
|
+
preparePageEventRequestOptions,
|
|
21
|
+
onRequest
|
|
20
22
|
}: RouteConfig = {}): Promise<{
|
|
21
23
|
plugin: typeof plugin
|
|
22
24
|
options: PluginOptions
|
|
@@ -52,7 +54,9 @@ export const configureEnginePlugin = async ({
|
|
|
52
54
|
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
53
55
|
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
54
56
|
},
|
|
55
|
-
viewContext: devtoolContext
|
|
57
|
+
viewContext: devtoolContext,
|
|
58
|
+
preparePageEventRequestOptions,
|
|
59
|
+
onRequest
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -11,7 +11,9 @@ const pluginRegistrationOptionsSchema = Joi.object({
|
|
|
11
11
|
baseLayoutPath: Joi.string().required(),
|
|
12
12
|
paths: Joi.array().items(Joi.string()).required()
|
|
13
13
|
}).required(),
|
|
14
|
-
viewContext: Joi.function().required()
|
|
14
|
+
viewContext: Joi.function().required(),
|
|
15
|
+
preparePageEventRequestOptions: Joi.function().optional(),
|
|
16
|
+
onRequest: Joi.function().optional()
|
|
15
17
|
})
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type FeaturedFormPageViewModel,
|
|
23
23
|
type FormContext,
|
|
24
24
|
type FormContextRequest,
|
|
25
|
-
type
|
|
25
|
+
type FormPayloadParams,
|
|
26
26
|
type FormSubmissionState,
|
|
27
27
|
type UploadStatusFileResponse,
|
|
28
28
|
type UploadStatusResponse
|
|
@@ -1063,7 +1063,7 @@ describe('FileUploadPageController', () => {
|
|
|
1063
1063
|
|
|
1064
1064
|
jest
|
|
1065
1065
|
.spyOn(controller, 'getFormParams')
|
|
1066
|
-
.mockReturnValue({ confirm: false } as unknown as
|
|
1066
|
+
.mockReturnValue({ confirm: false } as unknown as FormPayloadParams)
|
|
1067
1067
|
|
|
1068
1068
|
const proceedSpy = jest
|
|
1069
1069
|
.spyOn(controller, 'proceed')
|
|
@@ -26,8 +26,8 @@ import {
|
|
|
26
26
|
type FormContext,
|
|
27
27
|
type FormContextRequest,
|
|
28
28
|
type FormPageViewModel,
|
|
29
|
-
type FormParams,
|
|
30
29
|
type FormPayload,
|
|
30
|
+
type FormPayloadParams,
|
|
31
31
|
type FormState,
|
|
32
32
|
type FormStateValue,
|
|
33
33
|
type FormSubmissionState
|
|
@@ -268,7 +268,7 @@ export class QuestionPageController extends PageController {
|
|
|
268
268
|
/**
|
|
269
269
|
* Gets form params (from payload) for this page only
|
|
270
270
|
*/
|
|
271
|
-
getFormParams(request?: FormContextRequest):
|
|
271
|
+
getFormParams(request?: FormContextRequest): FormPayloadParams {
|
|
272
272
|
const { payload } = request ?? {}
|
|
273
273
|
|
|
274
274
|
const result = paramsSchema.validate(payload, {
|
|
@@ -276,7 +276,7 @@ export class QuestionPageController extends PageController {
|
|
|
276
276
|
stripUnknown: true
|
|
277
277
|
})
|
|
278
278
|
|
|
279
|
-
return result.value as
|
|
279
|
+
return result.value as FormPayloadParams
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
getStateFromValidForm(
|
|
@@ -34,7 +34,8 @@ export const plugin = {
|
|
|
34
34
|
keyGenerator,
|
|
35
35
|
sessionHydrator,
|
|
36
36
|
nunjucks: nunjucksOptions,
|
|
37
|
-
viewContext
|
|
37
|
+
viewContext,
|
|
38
|
+
preparePageEventRequestOptions
|
|
38
39
|
} = options
|
|
39
40
|
const cacheService = new CacheService({
|
|
40
41
|
server,
|
|
@@ -82,7 +83,11 @@ export const plugin = {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
const routes = [
|
|
85
|
-
...getQuestionRoutes(
|
|
86
|
+
...getQuestionRoutes(
|
|
87
|
+
getRouteOptions,
|
|
88
|
+
postRouteOptions,
|
|
89
|
+
preparePageEventRequestOptions
|
|
90
|
+
),
|
|
86
91
|
...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions),
|
|
87
92
|
...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions),
|
|
88
93
|
...getFileUploadStatusRoutes()
|
|
@@ -88,7 +88,7 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
|
|
|
88
88
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong
|
|
89
89
|
const prefix = server.realm.modifiers.route.prefix ?? ''
|
|
90
90
|
|
|
91
|
-
const { services = defaultServices, controllers } = options
|
|
91
|
+
const { services = defaultServices, controllers, onRequest } = options
|
|
92
92
|
|
|
93
93
|
const { formsService } = services
|
|
94
94
|
|
|
@@ -166,6 +166,11 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
|
|
|
166
166
|
server.app.models.set(key, item)
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// Call the onRequest callback if it has been supplied
|
|
170
|
+
if (onRequest) {
|
|
171
|
+
onRequest(request, params, item.model.def, metadata)
|
|
172
|
+
}
|
|
173
|
+
|
|
169
174
|
// Assign the model to the request data
|
|
170
175
|
// for use in the downstream handler
|
|
171
176
|
request.app.model = item.model
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
dispatchHandler,
|
|
20
20
|
redirectOrMakeHandler
|
|
21
21
|
} from '~/src/server/plugins/engine/routes/index.js'
|
|
22
|
+
import { type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js'
|
|
22
23
|
import {
|
|
23
24
|
type FormRequest,
|
|
24
25
|
type FormRequestPayload,
|
|
@@ -34,47 +35,57 @@ import {
|
|
|
34
35
|
} from '~/src/server/schemas/index.js'
|
|
35
36
|
import * as httpService from '~/src/server/services/httpService.js'
|
|
36
37
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
38
|
+
function makeGetHandler(
|
|
39
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
40
40
|
) {
|
|
41
|
-
|
|
41
|
+
return function getHandler(
|
|
42
|
+
request: FormRequest,
|
|
43
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
44
|
+
) {
|
|
45
|
+
const { params } = request
|
|
46
|
+
|
|
47
|
+
if (normalisePath(params.path) === '') {
|
|
48
|
+
return dispatchHandler(request, h)
|
|
49
|
+
}
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
return redirectOrMakeHandler(request, h, async (page, context) => {
|
|
52
|
+
// Check for a page onLoad HTTP event and if one exists,
|
|
53
|
+
// call it and assign the response to the context data
|
|
54
|
+
const { events } = page
|
|
55
|
+
const { model } = request.app
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const { events } = page
|
|
51
|
-
const { model } = request.app
|
|
57
|
+
if (!model) {
|
|
58
|
+
throw Boom.notFound(`No model found for /${params.path}`)
|
|
59
|
+
}
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
if (events?.onLoad && events.onLoad.type === 'http') {
|
|
62
|
+
const { options } = events.onLoad
|
|
63
|
+
const { url } = options
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
// TODO: Update structured data POST payload with when helper
|
|
66
|
+
// is updated to removing the dependency on `SummaryViewModel` etc.
|
|
67
|
+
const viewModel = new SummaryViewModel(request, page, context)
|
|
68
|
+
const items = getFormSubmissionData(
|
|
69
|
+
viewModel.context,
|
|
70
|
+
viewModel.details
|
|
71
|
+
)
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const items = getFormSubmissionData(viewModel.context, viewModel.details)
|
|
73
|
+
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
74
|
+
const payload = format(items, model, undefined, undefined)
|
|
75
|
+
const opts = { payload }
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
if (preparePageEventRequestOptions) {
|
|
78
|
+
preparePageEventRequestOptions(opts, events.onLoad, page, context)
|
|
79
|
+
}
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
payload
|
|
71
|
-
})
|
|
81
|
+
const { payload: response } = await httpService.postJson(url, opts)
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
Object.assign(context.data, response)
|
|
84
|
+
}
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
return page.makeGetRouteHandler()(request, context, h)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
function postHandler(
|
|
@@ -98,13 +109,14 @@ function postHandler(
|
|
|
98
109
|
|
|
99
110
|
export function getRoutes(
|
|
100
111
|
getRouteOptions: RouteOptions<FormRequestRefs>,
|
|
101
|
-
postRouteOptions: RouteOptions<FormRequestPayloadRefs
|
|
112
|
+
postRouteOptions: RouteOptions<FormRequestPayloadRefs>,
|
|
113
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
102
114
|
): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {
|
|
103
115
|
return [
|
|
104
116
|
{
|
|
105
117
|
method: 'get',
|
|
106
118
|
path: '/{slug}',
|
|
107
|
-
handler:
|
|
119
|
+
handler: makeGetHandler(preparePageEventRequestOptions),
|
|
108
120
|
options: {
|
|
109
121
|
...getRouteOptions,
|
|
110
122
|
validate: {
|
|
@@ -131,7 +143,7 @@ export function getRoutes(
|
|
|
131
143
|
{
|
|
132
144
|
method: 'get',
|
|
133
145
|
path: '/{slug}/{path}/{itemId?}',
|
|
134
|
-
handler:
|
|
146
|
+
handler: makeGetHandler(preparePageEventRequestOptions),
|
|
135
147
|
options: {
|
|
136
148
|
...getRouteOptions,
|
|
137
149
|
validate: {
|
|
@@ -146,7 +158,7 @@ export function getRoutes(
|
|
|
146
158
|
{
|
|
147
159
|
method: 'get',
|
|
148
160
|
path: '/preview/{state}/{slug}/{path}/{itemId?}',
|
|
149
|
-
handler:
|
|
161
|
+
handler: makeGetHandler(preparePageEventRequestOptions),
|
|
150
162
|
options: {
|
|
151
163
|
...getRouteOptions,
|
|
152
164
|
validate: {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ComponentDef,
|
|
3
|
+
type Event,
|
|
4
|
+
type FormDefinition,
|
|
5
|
+
type FormMetadata,
|
|
3
6
|
type Item,
|
|
4
7
|
type List,
|
|
5
8
|
type Page
|
|
@@ -20,9 +23,11 @@ import { type PageControllerClass } from '~/src/server/plugins/engine/pageContro
|
|
|
20
23
|
import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
21
24
|
import {
|
|
22
25
|
type FormAction,
|
|
26
|
+
type FormParams,
|
|
23
27
|
type FormRequest,
|
|
24
28
|
type FormRequestPayload
|
|
25
29
|
} from '~/src/server/routes/types.js'
|
|
30
|
+
import { type RequestOptions } from '~/src/server/services/httpService.js'
|
|
26
31
|
import { type Services } from '~/src/server/types.js'
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -70,7 +75,7 @@ export interface FormSubmissionError
|
|
|
70
75
|
text: string // e.g: 'Date field must be a real date'
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
export interface
|
|
78
|
+
export interface FormPayloadParams {
|
|
74
79
|
action?: FormAction
|
|
75
80
|
confirm?: true
|
|
76
81
|
crumb?: string
|
|
@@ -81,7 +86,7 @@ export interface FormParams {
|
|
|
81
86
|
* Form POST for question pages
|
|
82
87
|
* (after Joi has converted value types)
|
|
83
88
|
*/
|
|
84
|
-
export type FormPayload =
|
|
89
|
+
export type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>
|
|
85
90
|
|
|
86
91
|
export type FormValue =
|
|
87
92
|
| Item['value']
|
|
@@ -333,6 +338,20 @@ export interface ErrorMessageTemplateList {
|
|
|
333
338
|
advancedSettingsErrors: ErrorMessageTemplate[]
|
|
334
339
|
}
|
|
335
340
|
|
|
341
|
+
export type PreparePageEventRequestOptions = (
|
|
342
|
+
options: RequestOptions,
|
|
343
|
+
event: Event,
|
|
344
|
+
page: PageControllerClass,
|
|
345
|
+
context: FormContext
|
|
346
|
+
) => void
|
|
347
|
+
|
|
348
|
+
export type OnRequestCallback = (
|
|
349
|
+
request: FormRequest | FormRequestPayload,
|
|
350
|
+
params: FormParams,
|
|
351
|
+
definition: FormDefinition,
|
|
352
|
+
metadata: FormMetadata
|
|
353
|
+
) => void
|
|
354
|
+
|
|
336
355
|
export interface PluginOptions {
|
|
337
356
|
model?: FormModel
|
|
338
357
|
services?: Services
|
|
@@ -349,4 +368,6 @@ export interface PluginOptions {
|
|
|
349
368
|
paths: string[]
|
|
350
369
|
}
|
|
351
370
|
viewContext: PluginProperties['forms-engine-plugin']['viewContext']
|
|
371
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
372
|
+
onRequest?: OnRequestCallback
|
|
352
373
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Joi from 'joi'
|
|
2
2
|
|
|
3
|
-
import { type
|
|
3
|
+
import { type FormPayloadParams } from '~/src/server/plugins/engine/types.js'
|
|
4
4
|
import { FormAction, FormStatus } from '~/src/server/routes/types.js'
|
|
5
5
|
|
|
6
6
|
export const stateSchema = Joi.string<FormStatus>()
|
|
@@ -23,7 +23,7 @@ export const itemIdSchema = Joi.string().uuid().required()
|
|
|
23
23
|
export const crumbSchema = Joi.string().optional().allow('')
|
|
24
24
|
export const confirmSchema = Joi.boolean().empty(false)
|
|
25
25
|
|
|
26
|
-
export const paramsSchema = Joi.object<
|
|
26
|
+
export const paramsSchema = Joi.object<FormPayloadParams>()
|
|
27
27
|
.keys({
|
|
28
28
|
action: actionSchema,
|
|
29
29
|
confirm: confirmSchema,
|
package/src/server/types.ts
CHANGED
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
9
9
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
|
|
10
10
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
11
|
+
import {
|
|
12
|
+
type OnRequestCallback,
|
|
13
|
+
type PreparePageEventRequestOptions
|
|
14
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
11
15
|
import {
|
|
12
16
|
type FormRequestPayload,
|
|
13
17
|
type FormStatus
|
|
@@ -41,6 +45,8 @@ export interface RouteConfig {
|
|
|
41
45
|
enforceCsrf?: boolean
|
|
42
46
|
services?: Services
|
|
43
47
|
controllers?: Record<string, typeof PageController>
|
|
48
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
49
|
+
onRequest?: OnRequestCallback
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export interface OutputService {
|