@defra/forms-engine-plugin 1.0.2 → 1.0.4
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 +4 -2
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/options.d.ts +7 -0
- package/.server/server/plugins/engine/options.js +37 -0
- package/.server/server/plugins/engine/options.js.map +1 -0
- package/.server/server/plugins/engine/options.test.js +33 -0
- package/.server/server/plugins/engine/options.test.js.map +1 -0
- package/.server/server/plugins/engine/plugin.js +5 -2
- package/.server/server/plugins/engine/plugin.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 +4 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/types.d.ts +2 -0
- package/.server/server/types.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/configureEnginePlugin.ts +4 -2
- package/src/server/plugins/engine/options.js +38 -0
- package/src/server/plugins/engine/options.test.js +34 -0
- package/src/server/plugins/engine/plugin.ts +10 -2
- package/src/server/plugins/engine/routes/questions.ts +47 -35
- package/src/server/plugins/engine/types.ts +10 -0
- package/src/server/types.ts +2 -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 }?: RouteConfig) => Promise<{
|
|
6
6
|
plugin: typeof plugin;
|
|
7
7
|
options: PluginOptions;
|
|
8
8
|
}>;
|
|
@@ -10,7 +10,8 @@ export const configureEnginePlugin = async ({
|
|
|
10
10
|
formFileName,
|
|
11
11
|
formFilePath,
|
|
12
12
|
services,
|
|
13
|
-
controllers
|
|
13
|
+
controllers,
|
|
14
|
+
preparePageEventRequestOptions
|
|
14
15
|
} = {}) => {
|
|
15
16
|
let model;
|
|
16
17
|
if (formFileName && formFilePath) {
|
|
@@ -38,7 +39,8 @@ export const configureEnginePlugin = async ({
|
|
|
38
39
|
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
39
40
|
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
40
41
|
},
|
|
41
|
-
viewContext: devtoolContext
|
|
42
|
+
viewContext: devtoolContext,
|
|
43
|
+
preparePageEventRequestOptions
|
|
42
44
|
}
|
|
43
45
|
};
|
|
44
46
|
};
|
|
@@ -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","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}: 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 }\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;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,IAAIL,YAAY,IAAIC,YAAY,EAAE;IAChC,MAAMK,UAAU,GAAG,MAAMC,OAAO,CAACjB,IAAI,CAACW,YAAY,EAAED,YAAY,CAAC,CAAC;IAClE,MAAM;MAAEQ;IAAK,CAAC,GAAGjB,KAAK,CAACS,YAAY,CAAC;IAEpC,MAAMS,eAAe,GAAG,GAAGjB,WAAW,GAAGgB,IAAI,EAAE;IAE/CH,KAAK,GAAG,IAAIZ,SAAS,CACnBa,UAAU,EACV;MAAEI,QAAQ,EAAED;IAAgB,CAAC,EAC7BP,QAAQ,EACRC,WACF,CAAC;EACH;EAEA,OAAO;IACLT,MAAM;IACNiB,OAAO,EAAE;MACPN,KAAK;MACLH,QAAQ,EAAEA,QAAQ,IAAI;QACpB;QACA,GAAGP,eAAe;QAClBC,YAAY,EAAE,MAAMA,YAAY,CAAC;MACnC,CAAC;MACDO,WAAW;MACXS,SAAS,EAAE,SAAS;MACpBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAACzB,IAAI,CAACO,eAAe,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;MAC3D,CAAC;MACDmB,WAAW,EAAElB,cAAc;MAC3BM;IACF;EACF,CAAC;AACH,CAAC;AAED,OAAO,eAAeG,OAAOA,CAACU,UAAkB,EAAE;EAChD,MAAM;IAAEC;EAAI,CAAC,GAAG3B,KAAK,CAAC0B,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":[]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates the plugin options against the schema and returns the validated value.
|
|
3
|
+
* @param {PluginOptions} options
|
|
4
|
+
* @returns {PluginOptions}
|
|
5
|
+
*/
|
|
6
|
+
export function validatePluginOptions(options: PluginOptions): PluginOptions;
|
|
7
|
+
import type { PluginOptions } from '~/src/server/plugins/engine/types.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
const pluginRegistrationOptionsSchema = Joi.object({
|
|
3
|
+
model: Joi.object().optional(),
|
|
4
|
+
services: Joi.object().optional(),
|
|
5
|
+
controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
6
|
+
cacheName: Joi.string().optional(),
|
|
7
|
+
filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
8
|
+
pluginPath: Joi.string().optional(),
|
|
9
|
+
nunjucks: Joi.object({
|
|
10
|
+
baseLayoutPath: Joi.string().required(),
|
|
11
|
+
paths: Joi.array().items(Joi.string()).required()
|
|
12
|
+
}).required(),
|
|
13
|
+
viewContext: Joi.function().required(),
|
|
14
|
+
preparePageEventRequestOptions: Joi.function().optional()
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validates the plugin options against the schema and returns the validated value.
|
|
19
|
+
* @param {PluginOptions} options
|
|
20
|
+
* @returns {PluginOptions}
|
|
21
|
+
*/
|
|
22
|
+
export function validatePluginOptions(options) {
|
|
23
|
+
const result = pluginRegistrationOptionsSchema.validate(options, {
|
|
24
|
+
abortEarly: false
|
|
25
|
+
});
|
|
26
|
+
if (result.error) {
|
|
27
|
+
throw new Error('Invalid plugin options', result.error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
31
|
+
return result.value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @import { PluginOptions } from '~/src/server/plugins/engine/types.js'
|
|
36
|
+
*/
|
|
37
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +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","preparePageEventRequestOptions","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})\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;AAC1D,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASkB,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAGvB,+BAA+B,CAACwB,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":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { validatePluginOptions } from "./options.js";
|
|
2
|
+
describe('validatePluginOptions', () => {
|
|
3
|
+
it('returns the validated value for valid options', () => {
|
|
4
|
+
const validOptions = {
|
|
5
|
+
nunjucks: {
|
|
6
|
+
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
7
|
+
paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner
|
|
8
|
+
},
|
|
9
|
+
viewContext: () => {
|
|
10
|
+
return {
|
|
11
|
+
hello: 'world'
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
expect(validatePluginOptions(validOptions)).toEqual(validOptions);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test
|
|
20
|
+
*/
|
|
21
|
+
it('fails if a required attribute is missing', () => {
|
|
22
|
+
const invalidOptions = {
|
|
23
|
+
nunjucks: {
|
|
24
|
+
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
25
|
+
paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// @ts-expect-error -- add a test for JS users
|
|
30
|
+
expect(() => validatePluginOptions(invalidOptions)).toThrow('Invalid plugin options');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=options.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","expect","toEqual","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n }\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n const invalidOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n }\n\n // @ts-expect-error -- add a test for JS users\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B;IACF,CAAC;IAEDC,MAAM,CAACT,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACO,OAAO,CAACP,YAAY,CAAC;EACnE,CAAC,CAAC;;EAEF;AACF;AACA;EACED,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD,MAAMS,cAAc,GAAG;MACrBP,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAC;;IAED;IACAG,MAAM,CAAC,MAAMT,qBAAqB,CAACW,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validatePluginOptions } from "./options.js";
|
|
1
2
|
import { getRoutes as getFileUploadStatusRoutes } from "./routes/file-upload.js";
|
|
2
3
|
import { makeLoadFormPreHandler } from "./routes/index.js";
|
|
3
4
|
import { getRoutes as getQuestionRoutes } from "./routes/questions.js";
|
|
@@ -10,13 +11,15 @@ export const plugin = {
|
|
|
10
11
|
dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],
|
|
11
12
|
multiple: true,
|
|
12
13
|
async register(server, options) {
|
|
14
|
+
options = validatePluginOptions(options);
|
|
13
15
|
const {
|
|
14
16
|
model,
|
|
15
17
|
cacheName,
|
|
16
18
|
keyGenerator,
|
|
17
19
|
sessionHydrator,
|
|
18
20
|
nunjucks: nunjucksOptions,
|
|
19
|
-
viewContext
|
|
21
|
+
viewContext,
|
|
22
|
+
preparePageEventRequestOptions
|
|
20
23
|
} = options;
|
|
21
24
|
const cacheService = new CacheService({
|
|
22
25
|
server,
|
|
@@ -50,7 +53,7 @@ export const plugin = {
|
|
|
50
53
|
method: loadFormPreHandler
|
|
51
54
|
}]
|
|
52
55
|
};
|
|
53
|
-
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()];
|
|
54
57
|
server.route(routes); // TODO
|
|
55
58
|
}
|
|
56
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["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 { 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 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":[]}
|
|
@@ -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":"AAAA,SAASA,iBAAiB,EAAEC,UAAU,QAAQ,oBAAoB;AAClE,OAAOC,IAAI,MAAM,YAAY;AAM7B,OAAOC,GAAG,MAAM,KAAK;AAErB,SACEC,aAAa,EACbC,OAAO,EACPC,YAAY;AAEd,SAASC,gBAAgB;AACzB,SAASC,MAAM;AACf,SAASC,qBAAqB;AAC9B,SACEC,eAAe,EACfC,qBAAqB;AASvB,SACEC,YAAY,EACZC,WAAW,EACXC,YAAY,EACZC,UAAU,EACVC,WAAW;AAEb,OAAO,KAAKC,WAAW;AAEvB,SAASC,cAAcA,CACrBC,8BAA+D,EAC/D;EACA,OAAO,SAASC,UAAUA,CACxBC,OAAoB,EACpBC,CAA6C,EAC7C;IACA,MAAM;MAAEC;IAAO,CAAC,GAAGF,OAAO;IAE1B,IAAIjB,aAAa,CAACmB,MAAM,CAACC,IAAI,CAAC,KAAK,EAAE,EAAE;MACrC,OAAOd,eAAe,CAACW,OAAO,EAAEC,CAAC,CAAC;IACpC;IAEA,OAAOX,qBAAqB,CAACU,OAAO,EAAEC,CAAC,EAAE,OAAOG,IAAI,EAAEC,OAAO,KAAK;MAChE;MACA;MACA,MAAM;QAAEC;MAAO,CAAC,GAAGF,IAAI;MACvB,MAAM;QAAEG;MAAM,CAAC,GAAGP,OAAO,CAACQ,GAAG;MAE7B,IAAI,CAACD,KAAK,EAAE;QACV,MAAM1B,IAAI,CAAC4B,QAAQ,CAAC,uBAAuBP,MAAM,CAACC,IAAI,EAAE,CAAC;MAC3D;MAEA,IAAIG,MAAM,EAAEI,MAAM,IAAIJ,MAAM,CAACI,MAAM,CAACC,IAAI,KAAK,MAAM,EAAE;QACnD,MAAM;UAAEC;QAAQ,CAAC,GAAGN,MAAM,CAACI,MAAM;QACjC,MAAM;UAAEG;QAAI,CAAC,GAAGD,OAAO;;QAEvB;QACA;QACA,MAAME,SAAS,GAAG,IAAI5B,gBAAgB,CAACc,OAAO,EAAEI,IAAI,EAAEC,OAAO,CAAC;QAC9D,MAAMU,KAAK,GAAG3B,qBAAqB,CACjC0B,SAAS,CAACT,OAAO,EACjBS,SAAS,CAACE,OACZ,CAAC;;QAED;QACA,MAAMC,OAAO,GAAG9B,MAAM,CAAC4B,KAAK,EAAER,KAAK,EAAEW,SAAS,EAAEA,SAAS,CAAC;QAC1D,MAAMC,IAAI,GAAG;UAAEF;QAAQ,CAAC;QAExB,IAAInB,8BAA8B,EAAE;UAClCA,8BAA8B,CAACqB,IAAI,EAAEb,MAAM,CAACI,MAAM,EAAEN,IAAI,EAAEC,OAAO,CAAC;QACpE;QAEA,MAAM;UAAEY,OAAO,EAAEG;QAAS,CAAC,GAAG,MAAMxB,WAAW,CAACyB,QAAQ,CAACR,GAAG,EAAEM,IAAI,CAAC;QAEnEG,MAAM,CAACC,MAAM,CAAClB,OAAO,CAACmB,IAAI,EAAEJ,QAAQ,CAAC;MACvC;MAEA,OAAOhB,IAAI,CAACqB,mBAAmB,CAAC,CAAC,CAACzB,OAAO,EAAEK,OAAO,EAAEJ,CAAC,CAAC;IACxD,CAAC,CAAC;EACJ,CAAC;AACH;AAEA,SAASyB,WAAWA,CAClB1B,OAA2B,EAC3BC,CAA6C,EAC7C;EACA,MAAM;IAAE0B;EAAM,CAAC,GAAG3B,OAAO;EAEzB,OAAOV,qBAAqB,CAACU,OAAO,EAAEC,CAAC,EAAE,CAACG,IAAI,EAAEC,OAAO,KAAK;IAC1D,MAAM;MAAEuB;IAAQ,CAAC,GAAGxB,IAAI;IACxB,MAAM;MAAEyB;IAAc,CAAC,GAAGxB,OAAO;;IAEjC;IACA,IAAIwB,aAAa,IAAI,CAAClD,iBAAiB,CAACiD,OAAO,CAAC,EAAE;MAChD,OAAO5C,OAAO,CAACgB,OAAO,EAAEC,CAAC,EAAEhB,YAAY,CAACmB,IAAI,CAAC0B,IAAI,EAAEH,KAAK,CAAC,CAAC;IAC5D;IAEA,OAAOvB,IAAI,CAAC2B,oBAAoB,CAAC,CAAC,CAAC/B,OAAO,EAAEK,OAAO,EAAEJ,CAAC,CAAC;EACzD,CAAC,CAAC;AACJ;AAEA,OAAO,SAAS+B,SAASA,CACvBC,eAA8C,EAC9CC,gBAAsD,EACtDpC,8BAA+D,EACS;EACxE,OAAO,CACL;IACEqC,MAAM,EAAE,KAAK;IACbhC,IAAI,EAAE,SAAS;IACfiC,OAAO,EAAEvC,cAAc,CAACC,8BAA8B,CAAC;IACvDc,OAAO,EAAE;MACP,GAAGqB,eAAe;MAClBI,QAAQ,EAAE;QACRnC,MAAM,EAAEpB,GAAG,CAACwD,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBC,IAAI,EAAE5D;QACR,CAAC;MACH;IACF;EACF,CAAC,EACD;IACEuD,MAAM,EAAE,KAAK;IACbhC,IAAI,EAAE,yBAAyB;IAC/BiC,OAAO,EAAE/C,eAAe;IACxBuB,OAAO,EAAE;MACP,GAAGqB,eAAe;MAClBI,QAAQ,EAAE;QACRnC,MAAM,EAAEpB,GAAG,CAACwD,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBE,KAAK,EAAE9C,WAAW;UAClB6C,IAAI,EAAE5D;QACR,CAAC;MACH;IACF;EACF,CAAC,EACD;IACEuD,MAAM,EAAE,KAAK;IACbhC,IAAI,EAAE,0BAA0B;IAChCiC,OAAO,EAAEvC,cAAc,CAACC,8BAA8B,CAAC;IACvDc,OAAO,EAAE;MACP,GAAGqB,eAAe;MAClBI,QAAQ,EAAE;QACRnC,MAAM,EAAEpB,GAAG,CAACwD,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBC,IAAI,EAAE5D,UAAU;UAChBuB,IAAI,EAAET,UAAU;UAChBgD,MAAM,EAAEjD,YAAY,CAACkD,QAAQ,CAAC;QAChC,CAAC;MACH;IACF;EACF,CAAC,EACD;IACER,MAAM,EAAE,KAAK;IACbhC,IAAI,EAAE,0CAA0C;IAChDiC,OAAO,EAAEvC,cAAc,CAACC,8BAA8B,CAAC;IACvDc,OAAO,EAAE;MACP,GAAGqB,eAAe;MAClBI,QAAQ,EAAE;QACRnC,MAAM,EAAEpB,GAAG,CAACwD,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBE,KAAK,EAAE9C,WAAW;UAClB6C,IAAI,EAAE5D,UAAU;UAChBuB,IAAI,EAAET,UAAU;UAChBgD,MAAM,EAAEjD,YAAY,CAACkD,QAAQ,CAAC;QAChC,CAAC;MACH;IACF;EACF,CAAC,EACD;IACER,MAAM,EAAE,MAAM;IACdhC,IAAI,EAAE,0BAA0B;IAChCiC,OAAO,EAAEV,WAAW;IACpBd,OAAO,EAAE;MACP,GAAGsB,gBAAgB;MACnBG,QAAQ,EAAE;QACRnC,MAAM,EAAEpB,GAAG,CAACwD,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBC,IAAI,EAAE5D,UAAU;UAChBuB,IAAI,EAAET,UAAU;UAChBgD,MAAM,EAAEjD,YAAY,CAACkD,QAAQ,CAAC;QAChC,CAAC,CAAC;QACF1B,OAAO,EAAEnC,GAAG,CAACwD,MAAM,CAAC,CAAC,CAClBC,IAAI,CAAC;UACJK,KAAK,EAAEpD,WAAW;UAClBqD,MAAM,EAAEtD;QACV,CAAC,CAAC,CACDuD,OAAO,CAAC,IAAI,CAAC,CACbC,QAAQ,CAAC;MACd;IACF;EACF,CAAC,EACD;IACEZ,MAAM,EAAE,MAAM;IACdhC,IAAI,EAAE,0CAA0C;IAChDiC,OAAO,EAAEV,WAAW;IACpBd,OAAO,EAAE;MACP,GAAGsB,gBAAgB;MACnBG,QAAQ,EAAE;QACRnC,MAAM,EAAEpB,GAAG,CAACwD,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBE,KAAK,EAAE9C,WAAW;UAClB6C,IAAI,EAAE5D,UAAU;UAChBuB,IAAI,EAAET,UAAU;UAChBgD,MAAM,EAAEjD,YAAY,CAACkD,QAAQ,CAAC;QAChC,CAAC,CAAC;QACF1B,OAAO,EAAEnC,GAAG,CAACwD,MAAM,CAAC,CAAC,CAClBC,IAAI,CAAC;UACJK,KAAK,EAAEpD,WAAW;UAClBqD,MAAM,EAAEtD;QACV,CAAC,CAAC,CACDuD,OAAO,CAAC,IAAI,CAAC,CACbC,QAAQ,CAAC;MACd;IACF;EACF,CAAC,CACF;AACH","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 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';
|
|
@@ -8,6 +8,7 @@ import { type PageController } from '~/src/server/plugins/engine/pageControllers
|
|
|
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
10
|
import { type FormAction, 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:
|
|
@@ -257,6 +258,7 @@ 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;
|
|
260
262
|
export interface PluginOptions {
|
|
261
263
|
model?: FormModel;
|
|
262
264
|
services?: Services;
|
|
@@ -271,4 +273,5 @@ export interface PluginOptions {
|
|
|
271
273
|
paths: string[];
|
|
272
274
|
};
|
|
273
275
|
viewContext: PluginProperties['forms-engine-plugin']['viewContext'];
|
|
276
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions;
|
|
274
277
|
}
|
|
@@ -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 FormParams {\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 = FormParams & 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 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}\n"],"mappings":"
|
|
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 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 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 FormParams {\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 = FormParams & 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 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}\n"],"mappings":"AA6BA;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":[]}
|
|
@@ -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 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,7 @@ export interface RouteConfig {
|
|
|
25
26
|
enforceCsrf?: boolean;
|
|
26
27
|
services?: Services;
|
|
27
28
|
controllers?: Record<string, typeof PageController>;
|
|
29
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions;
|
|
28
30
|
}
|
|
29
31
|
export interface OutputService {
|
|
30
32
|
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 { type PreparePageEventRequestOptions } 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}\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
|
@@ -16,7 +16,8 @@ export const configureEnginePlugin = async ({
|
|
|
16
16
|
formFileName,
|
|
17
17
|
formFilePath,
|
|
18
18
|
services,
|
|
19
|
-
controllers
|
|
19
|
+
controllers,
|
|
20
|
+
preparePageEventRequestOptions
|
|
20
21
|
}: RouteConfig = {}): Promise<{
|
|
21
22
|
plugin: typeof plugin
|
|
22
23
|
options: PluginOptions
|
|
@@ -52,7 +53,8 @@ export const configureEnginePlugin = async ({
|
|
|
52
53
|
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
53
54
|
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
54
55
|
},
|
|
55
|
-
viewContext: devtoolContext
|
|
56
|
+
viewContext: devtoolContext,
|
|
57
|
+
preparePageEventRequestOptions
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
|
|
3
|
+
const pluginRegistrationOptionsSchema = Joi.object({
|
|
4
|
+
model: Joi.object().optional(),
|
|
5
|
+
services: Joi.object().optional(),
|
|
6
|
+
controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
7
|
+
cacheName: Joi.string().optional(),
|
|
8
|
+
filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
9
|
+
pluginPath: Joi.string().optional(),
|
|
10
|
+
nunjucks: Joi.object({
|
|
11
|
+
baseLayoutPath: Joi.string().required(),
|
|
12
|
+
paths: Joi.array().items(Joi.string()).required()
|
|
13
|
+
}).required(),
|
|
14
|
+
viewContext: Joi.function().required(),
|
|
15
|
+
preparePageEventRequestOptions: Joi.function().optional()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates the plugin options against the schema and returns the validated value.
|
|
20
|
+
* @param {PluginOptions} options
|
|
21
|
+
* @returns {PluginOptions}
|
|
22
|
+
*/
|
|
23
|
+
export function validatePluginOptions(options) {
|
|
24
|
+
const result = pluginRegistrationOptionsSchema.validate(options, {
|
|
25
|
+
abortEarly: false
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (result.error) {
|
|
29
|
+
throw new Error('Invalid plugin options', result.error)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
33
|
+
return result.value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @import { PluginOptions } from '~/src/server/plugins/engine/types.js'
|
|
38
|
+
*/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'
|
|
2
|
+
|
|
3
|
+
describe('validatePluginOptions', () => {
|
|
4
|
+
it('returns the validated value for valid options', () => {
|
|
5
|
+
const validOptions = {
|
|
6
|
+
nunjucks: {
|
|
7
|
+
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
8
|
+
paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner
|
|
9
|
+
},
|
|
10
|
+
viewContext: () => {
|
|
11
|
+
return { hello: 'world' }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
expect(validatePluginOptions(validOptions)).toEqual(validOptions)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test
|
|
20
|
+
*/
|
|
21
|
+
it('fails if a required attribute is missing', () => {
|
|
22
|
+
const invalidOptions = {
|
|
23
|
+
nunjucks: {
|
|
24
|
+
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
25
|
+
paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// @ts-expect-error -- add a test for JS users
|
|
30
|
+
expect(() => validatePluginOptions(invalidOptions)).toThrow(
|
|
31
|
+
'Invalid plugin options'
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '@hapi/hapi'
|
|
8
8
|
|
|
9
9
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
10
|
+
import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'
|
|
10
11
|
import { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'
|
|
11
12
|
import { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'
|
|
12
13
|
import { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'
|
|
@@ -25,13 +26,16 @@ export const plugin = {
|
|
|
25
26
|
dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],
|
|
26
27
|
multiple: true,
|
|
27
28
|
async register(server: Server, options: PluginOptions) {
|
|
29
|
+
options = validatePluginOptions(options)
|
|
30
|
+
|
|
28
31
|
const {
|
|
29
32
|
model,
|
|
30
33
|
cacheName,
|
|
31
34
|
keyGenerator,
|
|
32
35
|
sessionHydrator,
|
|
33
36
|
nunjucks: nunjucksOptions,
|
|
34
|
-
viewContext
|
|
37
|
+
viewContext,
|
|
38
|
+
preparePageEventRequestOptions
|
|
35
39
|
} = options
|
|
36
40
|
const cacheService = new CacheService({
|
|
37
41
|
server,
|
|
@@ -79,7 +83,11 @@ export const plugin = {
|
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const routes = [
|
|
82
|
-
...getQuestionRoutes(
|
|
86
|
+
...getQuestionRoutes(
|
|
87
|
+
getRouteOptions,
|
|
88
|
+
postRouteOptions,
|
|
89
|
+
preparePageEventRequestOptions
|
|
90
|
+
),
|
|
83
91
|
...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions),
|
|
84
92
|
...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions),
|
|
85
93
|
...getFileUploadStatusRoutes()
|
|
@@ -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,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ComponentDef,
|
|
3
|
+
type Event,
|
|
3
4
|
type Item,
|
|
4
5
|
type List,
|
|
5
6
|
type Page
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
type FormRequest,
|
|
24
25
|
type FormRequestPayload
|
|
25
26
|
} from '~/src/server/routes/types.js'
|
|
27
|
+
import { type RequestOptions } from '~/src/server/services/httpService.js'
|
|
26
28
|
import { type Services } from '~/src/server/types.js'
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -333,6 +335,13 @@ export interface ErrorMessageTemplateList {
|
|
|
333
335
|
advancedSettingsErrors: ErrorMessageTemplate[]
|
|
334
336
|
}
|
|
335
337
|
|
|
338
|
+
export type PreparePageEventRequestOptions = (
|
|
339
|
+
options: RequestOptions,
|
|
340
|
+
event: Event,
|
|
341
|
+
page: PageControllerClass,
|
|
342
|
+
context: FormContext
|
|
343
|
+
) => void
|
|
344
|
+
|
|
336
345
|
export interface PluginOptions {
|
|
337
346
|
model?: FormModel
|
|
338
347
|
services?: Services
|
|
@@ -349,4 +358,5 @@ export interface PluginOptions {
|
|
|
349
358
|
paths: string[]
|
|
350
359
|
}
|
|
351
360
|
viewContext: PluginProperties['forms-engine-plugin']['viewContext']
|
|
361
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
352
362
|
}
|
package/src/server/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ 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 { type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js'
|
|
11
12
|
import {
|
|
12
13
|
type FormRequestPayload,
|
|
13
14
|
type FormStatus
|
|
@@ -41,6 +42,7 @@ export interface RouteConfig {
|
|
|
41
42
|
enforceCsrf?: boolean
|
|
42
43
|
services?: Services
|
|
43
44
|
controllers?: Record<string, typeof PageController>
|
|
45
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export interface OutputService {
|