@defra/forms-engine-plugin 0.1.6 → 0.1.8
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/components/UkAddressField.js +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +8 -1
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +12 -1
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/referenceNumbers.js +17 -0
- package/.server/server/plugins/engine/referenceNumbers.js.map +1 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/components/UkAddressField.test.ts +41 -2
- package/src/server/plugins/engine/components/UkAddressField.ts +1 -1
- package/src/server/plugins/engine/helpers.test.ts +2 -1
- package/src/server/plugins/engine/models/FormModel.test.ts +56 -1
- package/src/server/plugins/engine/models/FormModel.ts +13 -1
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +3 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +1 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +2 -2
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +24 -12
- package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +4 -2
- package/src/server/plugins/engine/plugin.ts +18 -1
- package/src/server/plugins/engine/referenceNumbers.test.ts +33 -0
- package/src/server/plugins/engine/referenceNumbers.ts +18 -0
- package/src/server/plugins/engine/types.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UkAddressField.js","names":["ComponentType","ComponentCollection","FormComponent","isFormState","TextField","UkAddressField","constructor","def","props","name","options","isRequired","required","hideOptional","optionalText","hideTitle","collection","type","title","schema","max","autocomplete","classes","regex","parent","formSchema","stateSchema","getFormValueFromState","state","value","isState","undefined","getDisplayStringFromState","getContextValueFromState","join","Object","values","filter","Boolean","getViewModel","payload","errors","viewModel","components","fieldset","hint","label","legend","text","id","attributes","isUkAddress","isText","addressLine1","town","postcode"],"sources":["../../../../../src/server/plugins/engine/components/UkAddressField.ts"],"sourcesContent":["import { ComponentType, type UkAddressFieldComponent } from '@defra/forms-model'\nimport { type ObjectSchema } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { TextField } from '~/src/server/plugins/engine/components/TextField.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class UkAddressField extends FormComponent {\n declare options: UkAddressFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: UkAddressFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options } = def\n\n const isRequired = options.required !== false\n const hideOptional = !!options.optionalText\n const hideTitle = !!options.hideTitle\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.TextField,\n name: `${name}__addressLine1`,\n title: 'Address line 1',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-line1',\n required: isRequired,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__addressLine2`,\n title: 'Address line 2',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-line2',\n required: false,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__town`,\n title: 'Town or city',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-level2',\n classes: 'govuk-!-width-two-thirds',\n required: isRequired,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__county`,\n title: 'County',\n schema: { max: 100 },\n options: {\n autocomplete: '
|
|
1
|
+
{"version":3,"file":"UkAddressField.js","names":["ComponentType","ComponentCollection","FormComponent","isFormState","TextField","UkAddressField","constructor","def","props","name","options","isRequired","required","hideOptional","optionalText","hideTitle","collection","type","title","schema","max","autocomplete","classes","regex","parent","formSchema","stateSchema","getFormValueFromState","state","value","isState","undefined","getDisplayStringFromState","getContextValueFromState","join","Object","values","filter","Boolean","getViewModel","payload","errors","viewModel","components","fieldset","hint","label","legend","text","id","attributes","isUkAddress","isText","addressLine1","town","postcode"],"sources":["../../../../../src/server/plugins/engine/components/UkAddressField.ts"],"sourcesContent":["import { ComponentType, type UkAddressFieldComponent } from '@defra/forms-model'\nimport { type ObjectSchema } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { TextField } from '~/src/server/plugins/engine/components/TextField.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class UkAddressField extends FormComponent {\n declare options: UkAddressFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: UkAddressFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options } = def\n\n const isRequired = options.required !== false\n const hideOptional = !!options.optionalText\n const hideTitle = !!options.hideTitle\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.TextField,\n name: `${name}__addressLine1`,\n title: 'Address line 1',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-line1',\n required: isRequired,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__addressLine2`,\n title: 'Address line 2',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-line2',\n required: false,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__town`,\n title: 'Town or city',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-level2',\n classes: 'govuk-!-width-two-thirds',\n required: isRequired,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__county`,\n title: 'County',\n schema: { max: 100 },\n options: {\n autocomplete: 'address-level1',\n required: false,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n },\n {\n type: ComponentType.TextField,\n name: `${name}__postcode`,\n title: 'Postcode',\n schema: {\n regex: '^[a-zA-Z]{1,2}\\\\d[a-zA-Z\\\\d]?\\\\s?\\\\d[a-zA-Z]{2}$'\n },\n options: {\n autocomplete: 'postal-code',\n classes: 'govuk-input--width-10',\n required: isRequired,\n optionalText: !isRequired && (hideOptional || !hideTitle)\n }\n }\n ],\n { ...props, parent: this }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return this.isState(value) ? value : undefined\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n return this.getContextValueFromState(state)?.join(', ') ?? ''\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n if (!value) {\n return null\n }\n\n return Object.values(value).filter(Boolean)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const { collection, name, options } = this\n\n const viewModel = super.getViewModel(payload, errors)\n let { components, fieldset, hint, label } = viewModel\n\n fieldset ??= {\n legend: {\n text: label.text,\n\n /**\n * For screen readers, only hide legend visually. This can be overridden\n * by single component {@link QuestionPageController | `showTitle` handling}\n */\n classes: options.hideTitle\n ? 'govuk-visually-hidden'\n : 'govuk-fieldset__legend--m'\n }\n }\n\n if (hint) {\n hint.id ??= `${name}-hint`\n fieldset.attributes ??= {\n 'aria-describedby': hint.id\n }\n }\n\n components = collection.getViewModel(payload, errors)\n\n return {\n ...viewModel,\n fieldset,\n components\n }\n }\n\n isState(value?: FormStateValue | FormState): value is UkAddressState {\n return UkAddressField.isUkAddress(value)\n }\n\n static isUkAddress(\n value?: FormStateValue | FormState\n ): value is UkAddressState {\n return (\n isFormState(value) &&\n TextField.isText(value.addressLine1) &&\n TextField.isText(value.town) &&\n TextField.isText(value.postcode)\n )\n }\n}\n\nexport interface UkAddressState extends Record<string, string> {\n addressLine1: string\n addressLine2: string\n town: string\n county: string\n postcode: string\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -196,7 +196,8 @@ export class FormModel {
|
|
|
196
196
|
listDefMap: this.listDefMap,
|
|
197
197
|
componentDefMap: this.componentDefMap,
|
|
198
198
|
pageMap: this.pageMap,
|
|
199
|
-
componentMap: this.componentMap
|
|
199
|
+
componentMap: this.componentMap,
|
|
200
|
+
referenceNumber: getReferenceNumber(state)
|
|
200
201
|
};
|
|
201
202
|
|
|
202
203
|
// Validate current page
|
|
@@ -360,4 +361,10 @@ function validateFormState(request, page, context) {
|
|
|
360
361
|
}
|
|
361
362
|
return context;
|
|
362
363
|
}
|
|
364
|
+
function getReferenceNumber(state) {
|
|
365
|
+
if (!state.$$__referenceNumber || typeof state.$$__referenceNumber !== 'string') {
|
|
366
|
+
throw Error('Reference number not found in form state');
|
|
367
|
+
}
|
|
368
|
+
return state.$$__referenceNumber;
|
|
369
|
+
}
|
|
363
370
|
//# sourceMappingURL=FormModel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","formDefinitionSchema","hasComponents","hasRepeater","add","Parser","joi","findPage","getError","getPage","createPage","validationOptions","opts","defaultServices","FormAction","merge","FormModel","engine","def","lists","sections","name","values","basePath","conditions","pages","services","controllers","pageDefMap","listDefMap","componentDefMap","pageMap","componentMap","constructor","options","result","validate","abortEarly","error","structuredClone","value","push","title","type","items","text","forEach","conditionDef","condition","makeCondition","map","pageDef","some","controller","Status","path","Map","page","list","filter","flatMap","components","component","collection","makeSchema","makeFilteredSchema","relevantPages","schema","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","Date","toISOString","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","key","defineProperty","get","from","parse","toExpression","getList","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","getNextPath","validateFormState","assignPaths","V2","keys","getContextValueFromState","includes","action","getFormParams","Validate","update","fields","field","CheckboxesField","undefined","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n formDefinitionSchema,\n hasComponents,\n hasRepeater,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n findPage,\n getError,\n getPage\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\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\n constructor(\n def: typeof this.def,\n options: { basePath: string },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n const result = formDefinitionSchema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n text: 'Yes',\n value: true\n },\n {\n text: 'No',\n value: false\n }\n ]\n })\n\n this.engine = def.engine\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(conditionDef)\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections\n */\n makeSchema() {\n return this.makeFilteredSchema(this.pages)\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n return add(new Date(), { [timeUnit]: timePeriod }).toISOString()\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const key in conditions) {\n Object.defineProperty(context, key, {\n get() {\n return conditions[key]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(name: string): List | undefined {\n return this.lists.find((list) => list.name === name)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (nextPage.path === currentPath) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (!request.payload || action !== FormAction.Validate) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","formDefinitionSchema","hasComponents","hasRepeater","add","Parser","joi","findPage","getError","getPage","createPage","validationOptions","opts","defaultServices","FormAction","merge","FormModel","engine","def","lists","sections","name","values","basePath","conditions","pages","services","controllers","pageDefMap","listDefMap","componentDefMap","pageMap","componentMap","constructor","options","result","validate","abortEarly","error","structuredClone","value","push","title","type","items","text","forEach","conditionDef","condition","makeCondition","map","pageDef","some","controller","Status","path","Map","page","list","filter","flatMap","components","component","collection","makeSchema","makeFilteredSchema","relevantPages","schema","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","Date","toISOString","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","key","defineProperty","get","from","parse","toExpression","getList","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","getNextPath","validateFormState","assignPaths","V2","keys","getContextValueFromState","includes","action","getFormParams","Validate","update","fields","field","CheckboxesField","undefined","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n formDefinitionSchema,\n hasComponents,\n hasRepeater,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n findPage,\n getError,\n getPage\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\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\n constructor(\n def: typeof this.def,\n options: { basePath: string },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n const result = formDefinitionSchema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n text: 'Yes',\n value: true\n },\n {\n text: 'No',\n value: false\n }\n ]\n })\n\n this.engine = def.engine\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(conditionDef)\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections\n */\n makeSchema() {\n return this.makeFilteredSchema(this.pages)\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n return add(new Date(), { [timeUnit]: timePeriod }).toISOString()\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const key in conditions) {\n Object.defineProperty(context, key, {\n get() {\n return conditions[key]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(name: string): List | undefined {\n return this.lists.find((list) => list.name === name)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state)\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (nextPage.path === currentPath) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (!request.payload || action !== FormAction.Validate) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"AAAA,SACEA,aAAa,EACbC,eAAe,EACfC,cAAc,EACdC,cAAc,EACdC,MAAM,EACNC,oBAAoB,EACpBC,aAAa,EACbC,WAAW,QAQN,oBAAoB;AAC3B,SAASC,GAAG,QAAQ,UAAU;AAC9B,SAASC,MAAM,QAAoB,WAAW;AAC9C,OAAOC,GAAG,MAAM,KAAK;AAGrB,SACEC,QAAQ,EACRC,QAAQ,EACRC,OAAO;AAIT,SACEC,UAAU;AAGZ,SAASC,iBAAiB,IAAIC,IAAI;AAClC,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AACnB,SAASC,KAAK;AAGd,OAAO,MAAMC,SAAS,CAAC;EACrB;EACAC,MAAM;;EAEN;EACAC,GAAG;EAEHC,KAAK;EACLC,QAAQ,GAA+B,EAAE;EACzCC,IAAI;EACJC,MAAM;EACNC,QAAQ;EACRC,UAAU;EACVC,KAAK;EACLC,QAAQ;EAERC,WAAW;EACXC,UAAU;EACVC,UAAU;EACVC,eAAe;EACfC,OAAO;EACPC,YAAY;EAEZC,WAAWA,CACTf,GAAoB,EACpBgB,OAA6B,EAC7BR,QAAkB,GAAGb,eAAe,EACpCc,WAAmD,EACnD;IACA,MAAMQ,MAAM,GAAGlC,oBAAoB,CAACmC,QAAQ,CAAClB,GAAG,EAAE;MAAEmB,UAAU,EAAE;IAAM,CAAC,CAAC;IAExE,IAAIF,MAAM,CAACG,KAAK,EAAE;MAChB,MAAMH,MAAM,CAACG,KAAK;IACpB;;IAEA;IACA;IACApB,GAAG,GAAGqB,eAAe,CAACJ,MAAM,CAACK,KAAK,CAAC;;IAEnC;IACAtB,GAAG,CAACC,KAAK,CAACsB,IAAI,CAAC;MACbpB,IAAI,EAAE,SAAS;MACfqB,KAAK,EAAE,QAAQ;MACfC,IAAI,EAAE,SAAS;MACfC,KAAK,EAAE,CACL;QACEC,IAAI,EAAE,KAAK;QACXL,KAAK,EAAE;MACT,CAAC,EACD;QACEK,IAAI,EAAE,IAAI;QACVL,KAAK,EAAE;MACT,CAAC;IAEL,CAAC,CAAC;IAEF,IAAI,CAACvB,MAAM,GAAGC,GAAG,CAACD,MAAM;IACxB,IAAI,CAACC,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,KAAK,GAAGD,GAAG,CAACC,KAAK;IACtB,IAAI,CAACC,QAAQ,GAAGF,GAAG,CAACE,QAAQ;IAC5B,IAAI,CAACC,IAAI,GAAGH,GAAG,CAACG,IAAI,IAAI,EAAE;IAC1B,IAAI,CAACC,MAAM,GAAGa,MAAM,CAACK,KAAK;IAC1B,IAAI,CAACjB,QAAQ,GAAGW,OAAO,CAACX,QAAQ;IAChC,IAAI,CAACC,UAAU,GAAG,CAAC,CAAC;IACpB,IAAI,CAACE,QAAQ,GAAGA,QAAQ;IACxB,IAAI,CAACC,WAAW,GAAGA,WAAW;IAE9BT,GAAG,CAACM,UAAU,CAACsB,OAAO,CAAEC,YAAY,IAAK;MACvC,MAAMC,SAAS,GAAG,IAAI,CAACC,aAAa,CAACF,YAAY,CAAC;MAClD,IAAI,CAACvB,UAAU,CAACwB,SAAS,CAAC3B,IAAI,CAAC,GAAG2B,SAAS;IAC7C,CAAC,CAAC;IAEF,IAAI,CAACvB,KAAK,GAAGP,GAAG,CAACO,KAAK,CAACyB,GAAG,CAAEC,OAAO,IAAKzC,UAAU,CAAC,IAAI,EAAEyC,OAAO,CAAC,CAAC;IAElE,IACE,CAACjC,GAAG,CAACO,KAAK,CAAC2B,IAAI,CACb,CAAC;MAAEC;IAAW,CAAC;IACb;IACAA,UAAU,KAAKtD,cAAc,CAACuD,MAClC,CAAC,EACD;MACA,IAAI,CAAC7B,KAAK,CAACgB,IAAI,CACb/B,UAAU,CAAC,IAAI,EAAE;QACfgC,KAAK,EAAE,gBAAgB;QACvBa,IAAI,EAAEzD,cAAc,CAACwD,MAAM;QAC3BD,UAAU,EAAEtD,cAAc,CAACuD;MAC7B,CAAC,CACH,CAAC;IACH;IAEA,IAAI,CAAC1B,UAAU,GAAG,IAAI4B,GAAG,CAACtC,GAAG,CAACO,KAAK,CAACyB,GAAG,CAAEO,IAAI,IAAK,CAACA,IAAI,CAACF,IAAI,EAAEE,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC5B,UAAU,GAAG,IAAI2B,GAAG,CAACtC,GAAG,CAACC,KAAK,CAAC+B,GAAG,CAAEQ,IAAI,IAAK,CAACA,IAAI,CAACrC,IAAI,EAAEqC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC5B,eAAe,GAAG,IAAI0B,GAAG,CAC5BtC,GAAG,CAACO,KAAK,CACNkC,MAAM,CAACzD,aAAa,CAAC,CACrB0D,OAAO,CAAEH,IAAI,IACZA,IAAI,CAACI,UAAU,CAACX,GAAG,CAAEY,SAAS,IAAK,CAACA,SAAS,CAACzC,IAAI,EAAEyC,SAAS,CAAC,CAChE,CACJ,CAAC;IAED,IAAI,CAAC/B,OAAO,GAAG,IAAIyB,GAAG,CAAC,IAAI,CAAC/B,KAAK,CAACyB,GAAG,CAAEO,IAAI,IAAK,CAACA,IAAI,CAACF,IAAI,EAAEE,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAACzB,YAAY,GAAG,IAAIwB,GAAG,CACzB,IAAI,CAAC/B,KAAK,CAACmC,OAAO,CAAEH,IAAI,IACtBA,IAAI,CAACM,UAAU,CAACF,UAAU,CAACX,GAAG,CAAEY,SAAS,IAAK,CAC5CA,SAAS,CAACzC,IAAI,EACdyC,SAAS,CACV,CACH,CACF,CAAC;EACH;;EAEA;AACF;AACA;EACEE,UAAUA,CAAA,EAAG;IACX,OAAO,IAAI,CAACC,kBAAkB,CAAC,IAAI,CAACxC,KAAK,CAAC;EAC5C;;EAEA;AACF;AACA;AACA;EACEwC,kBAAkBA,CAACC,aAAoC,EAAE;IACvD;IACA;IACA,IAAIC,MAAM,GAAG7D,GAAG,CAAC8D,MAAM,CAAsB,CAAC,CAACC,QAAQ,CAAC,CAAC;IAEzDH,aAAa,CAACpB,OAAO,CAAEW,IAAI,IAAK;MAC9BU,MAAM,GAAGA,MAAM,CAACG,MAAM,CAACb,IAAI,CAACM,UAAU,CAACQ,WAAW,CAAC;IACrD,CAAC,CAAC;IAEF,OAAOJ,MAAM;EACf;;EAEA;AACF;AACA;AACA;EACElB,aAAaA,CAACD,SAA2B,EAAuB;IAC9D,MAAMwB,MAAM,GAAG,IAAInE,MAAM,CAAC;MACxBoE,SAAS,EAAE;QACTC,OAAO,EAAE;MACX;IACF,CAAC,CAAC;IAEFC,MAAM,CAACC,MAAM,CAACJ,MAAM,CAACK,SAAS,EAAE;MAC9BC,iBAAiBA,CAACC,UAAkB,EAAEC,QAAmB,EAAE;QACzD,OAAO5E,GAAG,CAAC,IAAI6E,IAAI,CAAC,CAAC,EAAE;UAAE,CAACD,QAAQ,GAAGD;QAAW,CAAC,CAAC,CAACG,WAAW,CAAC,CAAC;MAClE;IACF,CAAC,CAAC;IAEF,MAAM;MAAE7D,IAAI;MAAE8D,WAAW;MAAE3C;IAAM,CAAC,GAAGQ,SAAS;IAC9C,MAAMoC,IAAI,GAAG,IAAI,CAACC,qBAAqB,CAAC7C,KAAK,EAAEgC,MAAM,CAAC;IAEtD,MAAMc,EAAE,GAAIC,eAA0B,IAAK;MACzC,MAAMC,GAAG,GAAG,IAAI,CAACC,kBAAkB,CAACF,eAAe,EAAE,IAAI,CAAC/D,UAAU,CAAC;MACrE,IAAI;QACF,OAAO4D,IAAI,CAACM,QAAQ,CAACF,GAAG,CAAC;MAC3B,CAAC,CAAC,MAAM;QACN,OAAO,KAAK;MACd;IACF,CAAC;IAED,OAAO;MACLnE,IAAI;MACJ8D,WAAW;MACX3C,KAAK;MACL4C,IAAI;MACJE;IACF,CAAC;EACH;EAEAG,kBAAkBA,CAChBF,eAA0B,EAC1B/D,UAAwD,EACxD;IACA,MAAMmE,OAAO,GAAG;MAAE,GAAGJ;IAAgB,CAAC;IAEtC,KAAK,MAAMK,GAAG,IAAIpE,UAAU,EAAE;MAC5BmD,MAAM,CAACkB,cAAc,CAACF,OAAO,EAAEC,GAAG,EAAE;QAClCE,GAAGA,CAAA,EAAG;UACJ,OAAOtE,UAAU,CAACoE,GAAG,CAAC,EAAEN,EAAE,CAACC,eAAe,CAAC;QAC7C;MACF,CAAC,CAAC;IACJ;IAEA,OAAOI,OAAO;EAChB;EAEAN,qBAAqBA,CAAC7C,KAA0B,EAAEgC,MAAc,EAAE;IAChE,MAAMhD,UAAU,GAAG3B,eAAe,CAACkG,IAAI,CAACvD,KAAK,CAAC;IAC9C,OAAOgC,MAAM,CAACwB,KAAK,CAACxE,UAAU,CAACyE,YAAY,CAAC,CAAC,CAAC;EAChD;EAEAC,OAAOA,CAAC7E,IAAY,EAAoB;IACtC,OAAO,IAAI,CAACF,KAAK,CAACgF,IAAI,CAAEzC,IAAI,IAAKA,IAAI,CAACrC,IAAI,KAAKA,IAAI,CAAC;EACtD;;EAEA;AACF;AACA;EACE+E,cAAcA,CACZC,OAA2B,EAC3BC,KAAgB,EAChBC,MAA8B,EACjB;IACb,MAAM;MAAEC;IAAM,CAAC,GAAGH,OAAO;IAEzB,MAAM5C,IAAI,GAAGhD,OAAO,CAAC,IAAI,EAAE4F,OAAO,CAAC;;IAEnC;IACA,MAAMI,WAAW,GAAGhD,IAAI,CAACF,IAAI;IAC7B,MAAMmD,SAAS,GAAGjD,IAAI,CAACkD,YAAY,CAAC,CAAC;;IAErC;IACA,MAAMC,aAAa,GAAG,OAAO,IAAIJ,KAAK;IAEtC,IAAIb,OAAoB,GAAG;MACzBJ,eAAe,EAAE,CAAC,CAAC;MACnBsB,aAAa,EAAE,CAAC,CAAC;MACjB3C,aAAa,EAAE,EAAE;MACjB4C,OAAO,EAAErD,IAAI,CAACsD,oBAAoB,CAACV,OAAO,EAAEC,KAAK,CAAC;MAClDA,KAAK;MACLU,KAAK,EAAE,EAAE;MACTT,MAAM;MACNK,aAAa;MACbK,IAAI,EAAE,CAAC,CAAC;MACRrF,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,eAAe,EAAE,IAAI,CAACA,eAAe;MACrCC,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBC,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BkF,eAAe,EAAEC,kBAAkB,CAACb,KAAK;IAC3C,CAAC;;IAED;IACAX,OAAO,GAAGyB,mBAAmB,CAACf,OAAO,EAAE5C,IAAI,EAAEkC,OAAO,CAAC;;IAErD;IACA,IAAI0B,QAAQ,GAAG9G,QAAQ,CAAC,IAAI,EAAEmG,SAAS,CAAC;IAExC,IAAI,CAACY,iBAAiB,CAAC3B,OAAO,CAAC;;IAE/B;IACA,OAAO0B,QAAQ,EAAE;MACf;MACA1B,OAAO,CAACzB,aAAa,CAACzB,IAAI,CAAC4E,QAAQ,CAAC;MAEpC,IAAI,CAACE,qBAAqB,CAAC5B,OAAO,EAAE0B,QAAQ,CAAC;MAE7C,IAAI,CAACG,mBAAmB,CAAC7B,OAAO,EAAE0B,QAAQ,CAAC;;MAE3C;MACA,IAAIA,QAAQ,CAAC9D,IAAI,KAAKkD,WAAW,EAAE;QACjC;MACF;;MAEA;MACAY,QAAQ,GAAG9G,QAAQ,CAAC,IAAI,EAAE8G,QAAQ,CAACI,WAAW,CAAC9B,OAAO,CAAC,CAAC;IAC1D;;IAEA;IACAA,OAAO,GAAG+B,iBAAiB,CAACrB,OAAO,EAAE5C,IAAI,EAAEkC,OAAO,CAAC;;IAEnD;IACA,IAAI,CAACgC,WAAW,CAAChC,OAAO,CAAC;IAEzB,OAAOA,OAAO;EAChB;EAEQ2B,iBAAiBA,CAAC3B,OAAoB,EAAE;IAC9C;IACA;IACA;IACA,IAAI,IAAI,CAAC1E,MAAM,KAAKjB,MAAM,CAAC4H,EAAE,EAAE;MAC7B,KAAK,MAAMnE,IAAI,IAAI,IAAI,CAAChC,KAAK,EAAE;QAC7B,KAAK,MAAMmE,GAAG,IAAInC,IAAI,CAACoE,IAAI,EAAE;UAC3BlC,OAAO,CAACJ,eAAe,CAACK,GAAG,CAAC,GAAG,IAAI;QACrC;MACF;IACF;EACF;EAEQ2B,qBAAqBA,CAC3B5B,OAAoB,EACpBlC,IAAyB,EACzB;IACA,MAAM;MAAEM,UAAU;MAAEZ;IAAQ,CAAC,GAAGM,IAAI;IACpC;;IAEA,IAAI,CAACtD,WAAW,CAACgD,OAAO,CAAC,EAAE;MACzBwB,MAAM,CAACC,MAAM,CACXe,OAAO,CAACJ,eAAe,EACvBxB,UAAU,CAAC+D,wBAAwB,CAACnC,OAAO,CAACW,KAAK,CACnD,CAAC;IACH;EACF;EAEQkB,mBAAmBA,CAAC7B,OAAoB,EAAElC,IAAyB,EAAE;IAC3E;IACA,KAAK,MAAMmC,GAAG,IAAInC,IAAI,CAACoE,IAAI,EAAE;MAC3B,IAAI,OAAOlC,OAAO,CAACW,KAAK,CAACV,GAAG,CAAC,KAAK,WAAW,EAAE;QAC7CD,OAAO,CAACkB,aAAa,CAACjB,GAAG,CAAC,GAAGD,OAAO,CAACW,KAAK,CAACV,GAAG,CAAC;MACjD;IACF;EACF;EAEQ+B,WAAWA,CAAChC,OAAoB,EAAE;IACxC,KAAK,MAAM;MAAEkC,IAAI;MAAEtE;IAAK,CAAC,IAAIoC,OAAO,CAACzB,aAAa,EAAE;MAClDyB,OAAO,CAACqB,KAAK,CAACvE,IAAI,CAACc,IAAI,CAAC;;MAExB;MACA,IACEoC,OAAO,CAACY,MAAM,EAAEnD,IAAI,CAAC,CAAC;QAAE/B,IAAI;QAAEkC;MAAK,CAAC,KAAK;QACvC,OAAOsE,IAAI,CAACE,QAAQ,CAAC1G,IAAI,CAAC,IAAIwG,IAAI,CAACzE,IAAI,CAAEwC,GAAG,IAAKrC,IAAI,CAACwE,QAAQ,CAACnC,GAAG,CAAC,CAAC;MACtE,CAAC,CAAC,EACF;QACA;MACF;IACF;EACF;AACF;;AAEA;AACA;AACA;AACA,SAASwB,mBAAmBA,CAC1Bf,OAA2B,EAC3B5C,IAAyB,EACzBkC,OAAoB,EACP;EACb,MAAM;IAAE5B;EAAW,CAAC,GAAGN,IAAI;EAC3B,MAAM;IAAEqD,OAAO;IAAER;EAAM,CAAC,GAAGX,OAAO;EAElC,MAAM;IAAEqC;EAAO,CAAC,GAAGvE,IAAI,CAACwE,aAAa,CAAC5B,OAAO,CAAC;;EAE9C;EACA,IAAI,CAACA,OAAO,CAACS,OAAO,IAAIkB,MAAM,KAAKlH,UAAU,CAACoH,QAAQ,EAAE;IACtD,OAAOvC,OAAO;EAChB;;EAEA;EACA;EACA;EACA,MAAMwC,MAAM,GAAG;IAAE,GAAG9B,OAAO,CAACS;EAAQ,CAAC;EACrC/C,UAAU,CAACqE,MAAM,CAACtF,OAAO,CAAEuF,KAAK,IAAK;IACnC,IACEA,KAAK,CAAC1F,IAAI,KAAK/C,aAAa,CAAC0I,eAAe,IAC5C,EAAED,KAAK,CAAChH,IAAI,IAAI8G,MAAM,CAAC,EACvB;MACAA,MAAM,CAACE,KAAK,CAAChH,IAAI,CAAC,GAAGkH,SAAS;IAChC;EACF,CAAC,CAAC;EAEF,MAAM;IAAE/F,KAAK;IAAE+D;EAAO,CAAC,GAAGxC,UAAU,CAAC3B,QAAQ,CAAC;IAC5C,GAAG0E,OAAO;IACV,GAAGqB;EACL,CAAC,CAAC;;EAEF;EACA,MAAMK,SAAS,GAAG/E,IAAI,CAACgF,qBAAqB,CAACpC,OAAO,EAAEC,KAAK,EAAE9D,KAAK,CAAC;EAEnE,OAAO;IACL,GAAGmD,OAAO;IACVmB,OAAO,EAAE/F,KAAK,CAAC+F,OAAO,EAAEtE,KAAK,CAAC;IAC9B8D,KAAK,EAAEvF,KAAK,CAACuF,KAAK,EAAEkC,SAAS,CAAC;IAC9BjC;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA,SAASmB,iBAAiBA,CACxBrB,OAA2B,EAC3B5C,IAAyB,EACzBkC,OAAoB,EACP;EACb,MAAM;IAAEY,MAAM,GAAG,EAAE;IAAErC,aAAa;IAAE2C;EAAc,CAAC,GAAGlB,OAAO;;EAE7D;EACA,MAAM+C,aAAa,GAAGxE,aAAa,CAACP,MAAM,CACvCgF,YAAY,IAAKA,YAAY,KAAKlF,IACrC,CAAC;;EAED;EACA,MAAM;IAAEnB;EAAM,CAAC,GAAGmB,IAAI,CAACmF,KAAK,CACzB3E,kBAAkB,CAACyE,aAAa,CAAC,CACjCtG,QAAQ,CAACyE,aAAa,EAAE;IAAE,GAAGjG,IAAI;IAAEiI,YAAY,EAAE;EAAK,CAAC,CAAC;;EAE3D;EACA,IAAIvG,KAAK,EAAE;IACT,MAAMwG,WAAW,GAAGxG,KAAK,CAACyG,OAAO,CAAC7F,GAAG,CAAC1C,QAAQ,CAAC;IAC/C,OAAO;MAAE,GAAGmF,OAAO;MAAEY,MAAM,EAAEA,MAAM,CAACjC,MAAM,CAACwE,WAAW;IAAE,CAAC;EAC3D;EAEA,OAAOnD,OAAO;AAChB;AAEA,SAASwB,kBAAkBA,CAACb,KAAgB,EAAU;EACpD,IACE,CAACA,KAAK,CAAC0C,mBAAmB,IAC1B,OAAO1C,KAAK,CAAC0C,mBAAmB,KAAK,QAAQ,EAC7C;IACA,MAAMC,KAAK,CAAC,0CAA0C,CAAC;EACzD;EAEA,OAAO3C,KAAK,CAAC0C,mBAAmB;AAClC","ignoreList":[]}
|
|
@@ -12,6 +12,7 @@ import { format } from "./outputFormatters/machine/v1.js";
|
|
|
12
12
|
import { FileUploadPageController } from "./pageControllers/FileUploadPageController.js";
|
|
13
13
|
import { RepeatPageController } from "./pageControllers/RepeatPageController.js";
|
|
14
14
|
import { getFormSubmissionData } from "./pageControllers/SummaryPageController.js";
|
|
15
|
+
import { generateUniqueReference } from "./referenceNumbers.js";
|
|
15
16
|
import * as defaultServices from "./services/index.js";
|
|
16
17
|
import { getUploadStatus } from "./services/uploadService.js";
|
|
17
18
|
import { actionSchema, confirmSchema, crumbSchema, itemIdSchema, pathSchema, stateSchema } from "../../schemas/index.js";
|
|
@@ -174,7 +175,17 @@ export const plugin = {
|
|
|
174
175
|
}
|
|
175
176
|
const cacheService = getCacheService(request.server);
|
|
176
177
|
const page = getPage(model, request);
|
|
177
|
-
|
|
178
|
+
let state = await page.getState(request);
|
|
179
|
+
if (!state.$$__referenceNumber) {
|
|
180
|
+
const prefix = model.def.metadata?.referenceNumberPrefix ?? '';
|
|
181
|
+
if (typeof prefix !== 'string') {
|
|
182
|
+
throw Boom.badImplementation('Reference number prefix must be a string or undefined');
|
|
183
|
+
}
|
|
184
|
+
const referenceNumber = generateUniqueReference(prefix);
|
|
185
|
+
state = await page.mergeState(request, state, {
|
|
186
|
+
$$__referenceNumber: referenceNumber
|
|
187
|
+
});
|
|
188
|
+
}
|
|
178
189
|
const flash = cacheService.getFlash(request);
|
|
179
190
|
const context = model.getFormContext(request, state, flash?.errors);
|
|
180
191
|
const relevantPath = page.getRelevantPath(request, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["hasFormComponents","slugSchema","Boom","vision","isEqual","Joi","nunjucks","PREVIEW_PATH_PREFIX","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","normalisePath","proceed","redirectPath","PLUGIN_PATH","VIEW_PATH","context","prepareNunjucksEnvironment","FormModel","SummaryViewModel","format","FileUploadPageController","RepeatPageController","getFormSubmissionData","defaultServices","getUploadStatus","actionSchema","confirmSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","CacheService","plugin","name","dependencies","multiple","register","server","options","model","services","controllers","cacheName","viewPaths","filters","pluginPath","formsService","cacheService","path","Array","isArray","length","push","engines","html","compile","compileOptions","template","environment","render","prepare","next","configure","expose","app","itemCache","Map","models","loadFormPreHandler","request","h","continue","params","slug","isPreview","state","formState","metadata","getFormMetadata","id","notFound","key","item","get","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","set","dispatchHandler","servicePath","redirectOrMakeHandler","makeHandler","page","getState","flash","getFlash","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","startsWith","isForceAccess","redirectTo","query","returnUrl","getHref","getHandler","events","onLoad","type","url","viewModel","items","details","payload","undefined","response","postJson","Object","assign","data","makeGetRouteHandler","postHandler","pageDef","href","makePostRouteHandler","dispatchRouteOptions","pre","method","route","handler","validate","object","keys","getRouteOptions","itemId","optional","postRouteOptions","parse","crumb","action","unknown","required","getListSummaryHandler","makeGetListSummaryRouteHandler","postListSummaryHandler","makePostListSummaryRouteHandler","getItemDeleteHandler","makeGetItemDeleteRouteHandler","postItemDeleteHandler","makePostItemDeleteRouteHandler","confirm","uploadId","status","error","code","plugins","string","guid"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import { hasFormComponents, slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Plugin,\n type ResponseObject,\n type ResponseToolkit,\n type RouteOptions,\n type Server\n} from '@hapi/hapi'\nimport vision from '@hapi/vision'\nimport { isEqual } from 'date-fns'\nimport Joi from 'joi'\nimport nunjucks, { type Environment } from 'nunjucks'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n PLUGIN_PATH,\n VIEW_PATH,\n context,\n prepareNunjucksEnvironment\n} from '~/src/server/plugins/engine/index.js'\nimport {\n FormModel,\n SummaryViewModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FilterFunction,\n type FormContext\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 confirmSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\nimport { CacheService } from '~/src/server/services/index.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n viewPaths?: string[]\n filters?: Record<string, FilterFunction>\n pluginPath?: string\n}\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 services = defaultServices,\n controllers,\n cacheName,\n viewPaths,\n filters,\n pluginPath = PLUGIN_PATH\n } = options\n const { formsService } = services\n const cacheService = new CacheService(server, cacheName)\n\n // Paths array to tell `vision` and `nunjucks` where template files are stored.\n // We need to include `VIEW_PATH` in addition the runtime path (node_modules)\n // to keep the local tests working\n const path = [`${pluginPath}/${VIEW_PATH}`, VIEW_PATH]\n\n // Include any additional user provided view paths so our internal views engine\n // can find any files they provide from the consumer side if using custom `page.view`s\n if (Array.isArray(viewPaths) && viewPaths.length) {\n path.push(...viewPaths)\n }\n\n await server.register({\n plugin: vision,\n options: {\n engines: {\n html: {\n compile: (\n path: string,\n compileOptions: { environment: Environment }\n ) => {\n const template = nunjucks.compile(\n path,\n compileOptions.environment\n )\n\n return (context: object | undefined) => {\n return template.render(context)\n }\n },\n prepare: (\n options: EngineConfigurationObject,\n next: (err?: Error) => void\n ) => {\n // Nunjucks also needs an additional path configuration\n // to use the templates and macros from `govuk-frontend`\n const environment = nunjucks.configure([\n ...path,\n 'node_modules/govuk-frontend/dist'\n ])\n\n // Applies custom filters and globals for nunjucks\n // that are required by the `forms-engine-plugin`\n prepareNunjucksEnvironment(environment, filters)\n\n options.compileOptions.environment = environment\n\n next()\n }\n }\n },\n path,\n // Provides global context used with all templates\n context\n }\n })\n\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 = async (\n request: FormRequest | FormRequestPayload,\n h: Pick<ResponseToolkit, 'continue'>\n ) => {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params, path } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(path)\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 = itemCache.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(\n `Getting form definition ${id} (${slug}) ${formState}`\n )\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 =\n 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 = isPreview\n ? `${PREVIEW_PATH_PREFIX.substring(1)}/${formState}/${slug}`\n : slug\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 itemCache.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 const 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\n const redirectOrMakeHandler = async (\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 const state = await page.getState(request)\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\n const 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\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\n const 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\n const dispatchRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method: loadFormPreHandler\n }\n ]\n }\n\n server.route({\n method: 'get',\n path: '/{slug}',\n handler: dispatchHandler,\n options: {\n ...dispatchRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...dispatchRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n })\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method: loadFormPreHandler\n }\n ]\n }\n\n server.route({\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 server.route({\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 const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [{ method: loadFormPreHandler }]\n }\n\n server.route({\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 server.route({\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 * \"AddAnother\" repeat routes\n */\n\n // List summary GET route\n const getListSummaryHandler = (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n if (!(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makeGetListSummaryRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'get',\n path: '/{slug}/{path}/summary',\n handler: getListSummaryHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: getListSummaryHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n })\n\n // List summary POST route\n const postListSummaryHandler = (\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { isForceAccess } = context\n\n if (isForceAccess || !(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makePostListSummaryRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'post',\n path: '/{slug}/{path}/summary',\n handler: postListSummaryHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n })\n\n server.route({\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: postListSummaryHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n })\n\n // Item delete GET route\n const getItemDeleteHandler = (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n if (\n !(\n page instanceof RepeatPageController ||\n page instanceof FileUploadPageController\n )\n ) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page.makeGetItemDeleteRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'get',\n path: '/{slug}/{path}/{itemId}/confirm-delete',\n handler: getItemDeleteHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n })\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',\n handler: getItemDeleteHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n })\n }\n }\n })\n\n // Item delete POST route\n const postItemDeleteHandler = (\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { isForceAccess } = context\n\n if (\n isForceAccess ||\n !(\n page instanceof RepeatPageController ||\n page instanceof FileUploadPageController\n )\n ) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page.makePostItemDeleteRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'post',\n path: '/{slug}/{path}/{itemId}/confirm-delete',\n handler: postItemDeleteHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema,\n confirm: confirmSchema\n })\n .required()\n }\n }\n })\n\n server.route({\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',\n handler: postItemDeleteHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema,\n confirm: confirmSchema\n })\n .required()\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: async (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n ) => {\n try {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response(status)\n } catch (error) {\n request.logger.error(\n ['upload-status'],\n 'Upload status check failed',\n error\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n },\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n })\n }\n} satisfies Plugin<PluginOptions>\n\ninterface CompileOptions {\n environment: Environment\n}\n\nexport interface EngineConfigurationObject {\n compileOptions: CompileOptions\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["hasFormComponents","slugSchema","Boom","vision","isEqual","Joi","nunjucks","PREVIEW_PATH_PREFIX","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","normalisePath","proceed","redirectPath","PLUGIN_PATH","VIEW_PATH","context","prepareNunjucksEnvironment","FormModel","SummaryViewModel","format","FileUploadPageController","RepeatPageController","getFormSubmissionData","generateUniqueReference","defaultServices","getUploadStatus","actionSchema","confirmSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","CacheService","plugin","name","dependencies","multiple","register","server","options","model","services","controllers","cacheName","viewPaths","filters","pluginPath","formsService","cacheService","path","Array","isArray","length","push","engines","html","compile","compileOptions","template","environment","render","prepare","next","configure","expose","app","itemCache","Map","models","loadFormPreHandler","request","h","continue","params","slug","isPreview","state","formState","metadata","getFormMetadata","id","notFound","key","item","get","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","set","dispatchHandler","servicePath","redirectOrMakeHandler","makeHandler","page","getState","$$__referenceNumber","prefix","def","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","flash","getFlash","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","startsWith","isForceAccess","redirectTo","query","returnUrl","getHref","getHandler","events","onLoad","type","url","viewModel","items","details","payload","undefined","response","postJson","Object","assign","data","makeGetRouteHandler","postHandler","pageDef","href","makePostRouteHandler","dispatchRouteOptions","pre","method","route","handler","validate","object","keys","getRouteOptions","itemId","optional","postRouteOptions","parse","crumb","action","unknown","required","getListSummaryHandler","makeGetListSummaryRouteHandler","postListSummaryHandler","makePostListSummaryRouteHandler","getItemDeleteHandler","makeGetItemDeleteRouteHandler","postItemDeleteHandler","makePostItemDeleteRouteHandler","confirm","uploadId","status","error","code","plugins","string","guid"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import { hasFormComponents, slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Plugin,\n type ResponseObject,\n type ResponseToolkit,\n type RouteOptions,\n type Server\n} from '@hapi/hapi'\nimport vision from '@hapi/vision'\nimport { isEqual } from 'date-fns'\nimport Joi from 'joi'\nimport nunjucks, { type Environment } from 'nunjucks'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n PLUGIN_PATH,\n VIEW_PATH,\n context,\n prepareNunjucksEnvironment\n} from '~/src/server/plugins/engine/index.js'\nimport {\n FormModel,\n SummaryViewModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.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 { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FilterFunction,\n type FormContext\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 confirmSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\nimport { CacheService } from '~/src/server/services/index.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n viewPaths?: string[]\n filters?: Record<string, FilterFunction>\n pluginPath?: string\n}\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 services = defaultServices,\n controllers,\n cacheName,\n viewPaths,\n filters,\n pluginPath = PLUGIN_PATH\n } = options\n const { formsService } = services\n const cacheService = new CacheService(server, cacheName)\n\n // Paths array to tell `vision` and `nunjucks` where template files are stored.\n // We need to include `VIEW_PATH` in addition the runtime path (node_modules)\n // to keep the local tests working\n const path = [`${pluginPath}/${VIEW_PATH}`, VIEW_PATH]\n\n // Include any additional user provided view paths so our internal views engine\n // can find any files they provide from the consumer side if using custom `page.view`s\n if (Array.isArray(viewPaths) && viewPaths.length) {\n path.push(...viewPaths)\n }\n\n await server.register({\n plugin: vision,\n options: {\n engines: {\n html: {\n compile: (\n path: string,\n compileOptions: { environment: Environment }\n ) => {\n const template = nunjucks.compile(\n path,\n compileOptions.environment\n )\n\n return (context: object | undefined) => {\n return template.render(context)\n }\n },\n prepare: (\n options: EngineConfigurationObject,\n next: (err?: Error) => void\n ) => {\n // Nunjucks also needs an additional path configuration\n // to use the templates and macros from `govuk-frontend`\n const environment = nunjucks.configure([\n ...path,\n 'node_modules/govuk-frontend/dist'\n ])\n\n // Applies custom filters and globals for nunjucks\n // that are required by the `forms-engine-plugin`\n prepareNunjucksEnvironment(environment, filters)\n\n options.compileOptions.environment = environment\n\n next()\n }\n }\n },\n path,\n // Provides global context used with all templates\n context\n }\n })\n\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 = async (\n request: FormRequest | FormRequestPayload,\n h: Pick<ResponseToolkit, 'continue'>\n ) => {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params, path } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(path)\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 = itemCache.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(\n `Getting form definition ${id} (${slug}) ${formState}`\n )\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 =\n 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 = isPreview\n ? `${PREVIEW_PATH_PREFIX.substring(1)}/${formState}/${slug}`\n : slug\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 itemCache.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 const 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\n const redirectOrMakeHandler = async (\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\n const 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\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\n const 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\n const dispatchRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method: loadFormPreHandler\n }\n ]\n }\n\n server.route({\n method: 'get',\n path: '/{slug}',\n handler: dispatchHandler,\n options: {\n ...dispatchRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...dispatchRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n })\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method: loadFormPreHandler\n }\n ]\n }\n\n server.route({\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 server.route({\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 const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [{ method: loadFormPreHandler }]\n }\n\n server.route({\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 server.route({\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 * \"AddAnother\" repeat routes\n */\n\n // List summary GET route\n const getListSummaryHandler = (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n if (!(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makeGetListSummaryRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'get',\n path: '/{slug}/{path}/summary',\n handler: getListSummaryHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: getListSummaryHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n })\n\n // List summary POST route\n const postListSummaryHandler = (\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { isForceAccess } = context\n\n if (isForceAccess || !(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makePostListSummaryRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'post',\n path: '/{slug}/{path}/summary',\n handler: postListSummaryHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n })\n\n server.route({\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: postListSummaryHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n })\n\n // Item delete GET route\n const getItemDeleteHandler = (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n if (\n !(\n page instanceof RepeatPageController ||\n page instanceof FileUploadPageController\n )\n ) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page.makeGetItemDeleteRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'get',\n path: '/{slug}/{path}/{itemId}/confirm-delete',\n handler: getItemDeleteHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n })\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',\n handler: getItemDeleteHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n })\n }\n }\n })\n\n // Item delete POST route\n const postItemDeleteHandler = (\n request: FormRequestPayload,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { isForceAccess } = context\n\n if (\n isForceAccess ||\n !(\n page instanceof RepeatPageController ||\n page instanceof FileUploadPageController\n )\n ) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page.makePostItemDeleteRouteHandler()(request, context, h)\n })\n }\n\n server.route({\n method: 'post',\n path: '/{slug}/{path}/{itemId}/confirm-delete',\n handler: postItemDeleteHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema,\n confirm: confirmSchema\n })\n .required()\n }\n }\n })\n\n server.route({\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',\n handler: postItemDeleteHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema,\n confirm: confirmSchema\n })\n .required()\n }\n }\n })\n\n server.route({\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: async (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n ) => {\n try {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response(status)\n } catch (error) {\n request.logger.error(\n ['upload-status'],\n 'Upload status check failed',\n error\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n },\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n })\n }\n} satisfies Plugin<PluginOptions>\n\ninterface CompileOptions {\n environment: Environment\n}\n\nexport interface EngineConfigurationObject {\n compileOptions: CompileOptions\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.
|
|
5
|
+
* Provides no guarantee on uniqueness.
|
|
6
|
+
*/
|
|
7
|
+
export function generateUniqueReference(prefix) {
|
|
8
|
+
const segmentLength = 3;
|
|
9
|
+
const segmentCount = prefix ? 2 : 3;
|
|
10
|
+
prefix = prefix ? `${prefix}-` : '';
|
|
11
|
+
const segments = Array.from({
|
|
12
|
+
length: segmentCount
|
|
13
|
+
}, () => randomBytes(segmentLength).toString('hex').slice(0, segmentLength) // 0-9a-f, might be good enough?
|
|
14
|
+
);
|
|
15
|
+
return `${prefix}${segments.join('-')}`.toUpperCase();
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=referenceNumbers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"referenceNumbers.js","names":["randomBytes","generateUniqueReference","prefix","segmentLength","segmentCount","segments","Array","from","length","toString","slice","join","toUpperCase"],"sources":["../../../../src/server/plugins/engine/referenceNumbers.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\n\n/**\n * Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.\n * Provides no guarantee on uniqueness.\n */\nexport function generateUniqueReference(prefix?: string) {\n const segmentLength = 3\n const segmentCount = prefix ? 2 : 3\n prefix = prefix ? `${prefix}-` : ''\n\n const segments = Array.from(\n { length: segmentCount },\n () => randomBytes(segmentLength).toString('hex').slice(0, segmentLength) // 0-9a-f, might be good enough?\n )\n\n return `${prefix}${segments.join('-')}`.toUpperCase()\n}\n"],"mappings":"AAAA,SAASA,WAAW,QAAQ,aAAa;;AAEzC;AACA;AACA;AACA;AACA,OAAO,SAASC,uBAAuBA,CAACC,MAAe,EAAE;EACvD,MAAMC,aAAa,GAAG,CAAC;EACvB,MAAMC,YAAY,GAAGF,MAAM,GAAG,CAAC,GAAG,CAAC;EACnCA,MAAM,GAAGA,MAAM,GAAG,GAAGA,MAAM,GAAG,GAAG,EAAE;EAEnC,MAAMG,QAAQ,GAAGC,KAAK,CAACC,IAAI,CACzB;IAAEC,MAAM,EAAEJ;EAAa,CAAC,EACxB,MAAMJ,WAAW,CAACG,aAAa,CAAC,CAACM,QAAQ,CAAC,KAAK,CAAC,CAACC,KAAK,CAAC,CAAC,EAAEP,aAAa,CAAC,CAAC;EAC3E,CAAC;EAED,OAAO,GAAGD,MAAM,GAAGG,QAAQ,CAACM,IAAI,CAAC,GAAG,CAAC,EAAE,CAACC,WAAW,CAAC,CAAC;AACvD","ignoreList":[]}
|
|
@@ -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 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 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 { type FormAction, type FormRequest } from '~/src/server/routes/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}\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\n"],"mappings":"AAoBA;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;;
|
|
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 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 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 { type FormAction, type FormRequest } from '~/src/server/routes/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\n"],"mappings":"AAoBA;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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ComponentType,
|
|
3
|
+
type TextFieldComponent,
|
|
4
|
+
type UkAddressFieldComponent
|
|
5
|
+
} from '@defra/forms-model'
|
|
2
6
|
|
|
3
7
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
8
|
+
import { UkAddressField } from '~/src/server/plugins/engine/components/UkAddressField.js'
|
|
4
9
|
import {
|
|
5
10
|
getAnswer,
|
|
6
11
|
type Field
|
|
@@ -272,6 +277,40 @@ describe('UkAddressField', () => {
|
|
|
272
277
|
expect(result2.errors).toBeTruthy()
|
|
273
278
|
expect(result3.errors).toBeTruthy()
|
|
274
279
|
})
|
|
280
|
+
|
|
281
|
+
it('should ensure all address fields have valid autocomplete values', () => {
|
|
282
|
+
const ukAddressField = new UkAddressField(
|
|
283
|
+
{
|
|
284
|
+
type: ComponentType.UkAddressField,
|
|
285
|
+
name: 'testAddress',
|
|
286
|
+
title: 'Test Address',
|
|
287
|
+
options: {
|
|
288
|
+
required: true,
|
|
289
|
+
hideTitle: false
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
model
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
const expectedAutocompleteValues = [
|
|
298
|
+
'address-line1',
|
|
299
|
+
'address-line2',
|
|
300
|
+
'address-level1',
|
|
301
|
+
'address-level2',
|
|
302
|
+
'postal-code'
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
ukAddressField.collection.components.forEach((component) => {
|
|
306
|
+
const addressFieldOptions =
|
|
307
|
+
component.options as TextFieldComponent['options']
|
|
308
|
+
|
|
309
|
+
expect(expectedAutocompleteValues).toContain(
|
|
310
|
+
addressFieldOptions.autocomplete
|
|
311
|
+
)
|
|
312
|
+
})
|
|
313
|
+
})
|
|
275
314
|
})
|
|
276
315
|
|
|
277
316
|
describe('State', () => {
|
|
@@ -396,7 +435,7 @@ describe('UkAddressField', () => {
|
|
|
396
435
|
expect.objectContaining({
|
|
397
436
|
model: getViewModel(address, 'county', {
|
|
398
437
|
label: { text: 'County (optional)' },
|
|
399
|
-
attributes: { autocomplete: '
|
|
438
|
+
attributes: { autocomplete: 'address-level1' },
|
|
400
439
|
value: address.county
|
|
401
440
|
})
|
|
402
441
|
}),
|
|
@@ -561,7 +561,8 @@ describe('Helpers', () => {
|
|
|
561
561
|
listDefMap: model.listDefMap,
|
|
562
562
|
componentDefMap: model.componentDefMap,
|
|
563
563
|
pageMap: model.pageMap,
|
|
564
|
-
componentMap: model.componentMap
|
|
564
|
+
componentMap: model.componentMap,
|
|
565
|
+
referenceNumber: 'foobar'
|
|
565
566
|
}
|
|
566
567
|
})
|
|
567
568
|
|
|
@@ -18,7 +18,10 @@ describe('FormModel', () => {
|
|
|
18
18
|
basePath: '/components'
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
const state = {
|
|
21
|
+
const state = {
|
|
22
|
+
$$__referenceNumber: 'foobar',
|
|
23
|
+
checkboxesSingle: ['Arabian', 'Shetland']
|
|
24
|
+
}
|
|
22
25
|
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
23
26
|
|
|
24
27
|
const request: FormContextRequest = {
|
|
@@ -37,6 +40,58 @@ describe('FormModel', () => {
|
|
|
37
40
|
expect(context.errors).toContainEqual(
|
|
38
41
|
expect.objectContaining({ name: 'checkboxesSingle' })
|
|
39
42
|
)
|
|
43
|
+
expect(context.referenceNumber).toEqual(expect.any(String))
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('handles missing reference numbers', () => {
|
|
47
|
+
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
48
|
+
basePath: '/components'
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const state = {
|
|
52
|
+
checkboxesSingle: ['Arabian', 'Shetland']
|
|
53
|
+
}
|
|
54
|
+
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
55
|
+
|
|
56
|
+
const request: FormContextRequest = {
|
|
57
|
+
method: 'post',
|
|
58
|
+
payload: { crumb: 'dummyCrumb', action: 'validate' },
|
|
59
|
+
query: {},
|
|
60
|
+
path: pageUrl.pathname,
|
|
61
|
+
params: { path: 'components', slug: 'fields-required' },
|
|
62
|
+
url: pageUrl,
|
|
63
|
+
app: { model: formModel }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect(() => formModel.getFormContext(request, state)).toThrow(
|
|
67
|
+
'Reference number not found in form state'
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('handles non-string reference numbers', () => {
|
|
72
|
+
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
73
|
+
basePath: '/components'
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const state = {
|
|
77
|
+
$$__referenceNumber: 1232456,
|
|
78
|
+
checkboxesSingle: ['Arabian', 'Shetland']
|
|
79
|
+
}
|
|
80
|
+
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
81
|
+
|
|
82
|
+
const request: FormContextRequest = {
|
|
83
|
+
method: 'post',
|
|
84
|
+
payload: { crumb: 'dummyCrumb', action: 'validate' },
|
|
85
|
+
query: {},
|
|
86
|
+
path: pageUrl.pathname,
|
|
87
|
+
params: { path: 'components', slug: 'fields-required' },
|
|
88
|
+
url: pageUrl,
|
|
89
|
+
app: { model: formModel }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
expect(() => formModel.getFormContext(request, state)).toThrow(
|
|
93
|
+
'Reference number not found in form state'
|
|
94
|
+
)
|
|
40
95
|
})
|
|
41
96
|
})
|
|
42
97
|
})
|
|
@@ -275,7 +275,8 @@ export class FormModel {
|
|
|
275
275
|
listDefMap: this.listDefMap,
|
|
276
276
|
componentDefMap: this.componentDefMap,
|
|
277
277
|
pageMap: this.pageMap,
|
|
278
|
-
componentMap: this.componentMap
|
|
278
|
+
componentMap: this.componentMap,
|
|
279
|
+
referenceNumber: getReferenceNumber(state)
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
// Validate current page
|
|
@@ -441,3 +442,14 @@ function validateFormState(
|
|
|
441
442
|
|
|
442
443
|
return context
|
|
443
444
|
}
|
|
445
|
+
|
|
446
|
+
function getReferenceNumber(state: FormState): string {
|
|
447
|
+
if (
|
|
448
|
+
!state.$$__referenceNumber ||
|
|
449
|
+
typeof state.$$__referenceNumber !== 'string'
|
|
450
|
+
) {
|
|
451
|
+
throw Error('Reference number not found in form state')
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return state.$$__referenceNumber
|
|
455
|
+
}
|
|
@@ -51,6 +51,7 @@ describe('SummaryViewModel', () => {
|
|
|
51
51
|
{
|
|
52
52
|
description: '0 items',
|
|
53
53
|
state: {
|
|
54
|
+
$$__referenceNumber: 'foobar',
|
|
54
55
|
orderType: 'collection',
|
|
55
56
|
pizza: []
|
|
56
57
|
} satisfies FormState,
|
|
@@ -60,6 +61,7 @@ describe('SummaryViewModel', () => {
|
|
|
60
61
|
{
|
|
61
62
|
description: '1 item',
|
|
62
63
|
state: {
|
|
64
|
+
$$__referenceNumber: 'foobar',
|
|
63
65
|
orderType: 'delivery',
|
|
64
66
|
pizza: [
|
|
65
67
|
{
|
|
@@ -75,6 +77,7 @@ describe('SummaryViewModel', () => {
|
|
|
75
77
|
{
|
|
76
78
|
description: '2 items',
|
|
77
79
|
state: {
|
|
80
|
+
$$__referenceNumber: 'foobar',
|
|
78
81
|
orderType: 'delivery',
|
|
79
82
|
pizza: [
|
|
80
83
|
{
|
|
@@ -167,13 +167,13 @@ describe('PageController', () => {
|
|
|
167
167
|
|
|
168
168
|
await controller1.makeGetRouteHandler()(
|
|
169
169
|
request,
|
|
170
|
-
model.getFormContext(request, {}),
|
|
170
|
+
model.getFormContext(request, { $$__referenceNumber: 'foobar' }),
|
|
171
171
|
h
|
|
172
172
|
)
|
|
173
173
|
|
|
174
174
|
await controller2.makeGetRouteHandler()(
|
|
175
175
|
request,
|
|
176
|
-
model.getFormContext(request, {}),
|
|
176
|
+
model.getFormContext(request, { $$__referenceNumber: 'foobar' }),
|
|
177
177
|
h
|
|
178
178
|
)
|
|
179
179
|
|
|
@@ -178,12 +178,12 @@ describe('QuestionPageController', () => {
|
|
|
178
178
|
beforeEach(() => {
|
|
179
179
|
viewModel1 = controller1.getViewModel(
|
|
180
180
|
requestPage1,
|
|
181
|
-
model.getFormContext(requestPage1, {})
|
|
181
|
+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' })
|
|
182
182
|
)
|
|
183
183
|
|
|
184
184
|
viewModel2 = controller2.getViewModel(
|
|
185
185
|
requestPage2,
|
|
186
|
-
model.getFormContext(requestPage2, {})
|
|
186
|
+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' })
|
|
187
187
|
)
|
|
188
188
|
})
|
|
189
189
|
|
|
@@ -267,6 +267,7 @@ describe('QuestionPageController', () => {
|
|
|
267
267
|
|
|
268
268
|
// The state below shows we said we had a UKPassport and entered details for an applicant
|
|
269
269
|
const state: FormSubmissionState = {
|
|
270
|
+
$$__referenceNumber: 'foobar',
|
|
270
271
|
ukPassport: true,
|
|
271
272
|
numberOfApplicants: 2,
|
|
272
273
|
applicantOneFirstName: 'Enrique',
|
|
@@ -384,6 +385,7 @@ describe('QuestionPageController', () => {
|
|
|
384
385
|
} satisfies FormContextRequest
|
|
385
386
|
|
|
386
387
|
const context = controller.model.getFormContext(request, {
|
|
388
|
+
$$__referenceNumber: 'foobar',
|
|
387
389
|
dateField__day: 5,
|
|
388
390
|
dateField__month: 1,
|
|
389
391
|
dateField__year: 2024
|
|
@@ -411,7 +413,7 @@ describe('QuestionPageController', () => {
|
|
|
411
413
|
const controller = new QuestionPageController(model, pages[0])
|
|
412
414
|
|
|
413
415
|
// The state below shows we said we had a UKPassport and entered details for an applicant
|
|
414
|
-
const state: FormSubmissionState = {}
|
|
416
|
+
const state: FormSubmissionState = { $$__referenceNumber: 'foobar' }
|
|
415
417
|
|
|
416
418
|
const request = {
|
|
417
419
|
method: 'get',
|
|
@@ -467,7 +469,7 @@ describe('QuestionPageController', () => {
|
|
|
467
469
|
const controller = new QuestionPageController(model, pages[0])
|
|
468
470
|
|
|
469
471
|
// The state below shows we said we had a UKPassport and entered details for an applicant
|
|
470
|
-
const state: FormSubmissionState = {}
|
|
472
|
+
const state: FormSubmissionState = { $$__referenceNumber: 'foobar' }
|
|
471
473
|
|
|
472
474
|
const request = {
|
|
473
475
|
method: 'get',
|
|
@@ -531,15 +533,19 @@ describe('QuestionPageController', () => {
|
|
|
531
533
|
|
|
532
534
|
beforeEach(() => {
|
|
533
535
|
// Empty state
|
|
534
|
-
context = model.getFormContext(requestPage1, {
|
|
536
|
+
context = model.getFormContext(requestPage1, {
|
|
537
|
+
$$__referenceNumber: 'foobar'
|
|
538
|
+
})
|
|
535
539
|
|
|
536
540
|
// Question 1: Selected 'No'
|
|
537
541
|
contextNo = model.getFormContext(requestPage1, {
|
|
542
|
+
$$__referenceNumber: 'foobar',
|
|
538
543
|
yesNoField: false
|
|
539
544
|
})
|
|
540
545
|
|
|
541
546
|
// Question 1: Selected 'Yes'
|
|
542
547
|
contextYes = model.getFormContext(requestPage1, {
|
|
548
|
+
$$__referenceNumber: 'foobar',
|
|
543
549
|
yesNoField: true
|
|
544
550
|
})
|
|
545
551
|
})
|
|
@@ -619,13 +625,13 @@ describe('QuestionPageController', () => {
|
|
|
619
625
|
|
|
620
626
|
await controller1.makeGetRouteHandler()(
|
|
621
627
|
requestPage1,
|
|
622
|
-
model.getFormContext(requestPage1, {}),
|
|
628
|
+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' }),
|
|
623
629
|
h
|
|
624
630
|
)
|
|
625
631
|
|
|
626
632
|
await controller2.makeGetRouteHandler()(
|
|
627
633
|
requestPage2,
|
|
628
|
-
model.getFormContext(requestPage2, {}),
|
|
634
|
+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' }),
|
|
629
635
|
h
|
|
630
636
|
)
|
|
631
637
|
|
|
@@ -856,12 +862,12 @@ describe('QuestionPageController V2', () => {
|
|
|
856
862
|
beforeEach(() => {
|
|
857
863
|
viewModel1 = controller1.getViewModel(
|
|
858
864
|
requestPage1,
|
|
859
|
-
model.getFormContext(requestPage1, {})
|
|
865
|
+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' })
|
|
860
866
|
)
|
|
861
867
|
|
|
862
868
|
viewModel2 = controller2.getViewModel(
|
|
863
869
|
requestPage2,
|
|
864
|
-
model.getFormContext(requestPage2, {})
|
|
870
|
+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' })
|
|
865
871
|
)
|
|
866
872
|
})
|
|
867
873
|
|
|
@@ -945,6 +951,7 @@ describe('QuestionPageController V2', () => {
|
|
|
945
951
|
|
|
946
952
|
// The state below shows we said we had a UKPassport and entered details for an applicant
|
|
947
953
|
const state: FormSubmissionState = {
|
|
954
|
+
$$__referenceNumber: 'foobar',
|
|
948
955
|
ukPassport: true,
|
|
949
956
|
numberOfApplicants: 2,
|
|
950
957
|
applicantOneFirstName: 'Enrique',
|
|
@@ -1062,6 +1069,7 @@ describe('QuestionPageController V2', () => {
|
|
|
1062
1069
|
} satisfies FormContextRequest
|
|
1063
1070
|
|
|
1064
1071
|
const context = controller.model.getFormContext(request, {
|
|
1072
|
+
$$__referenceNumber: 'foobar',
|
|
1065
1073
|
dateField__day: 5,
|
|
1066
1074
|
dateField__month: 1,
|
|
1067
1075
|
dateField__year: 2024
|
|
@@ -1097,15 +1105,19 @@ describe('QuestionPageController V2', () => {
|
|
|
1097
1105
|
|
|
1098
1106
|
beforeEach(() => {
|
|
1099
1107
|
// Empty state
|
|
1100
|
-
context = model.getFormContext(requestPage1, {
|
|
1108
|
+
context = model.getFormContext(requestPage1, {
|
|
1109
|
+
$$__referenceNumber: 'foobar'
|
|
1110
|
+
})
|
|
1101
1111
|
|
|
1102
1112
|
// Question 1: Selected 'No'
|
|
1103
1113
|
contextNo = model.getFormContext(requestPage1, {
|
|
1114
|
+
$$__referenceNumber: 'foobar',
|
|
1104
1115
|
yesNoField: false
|
|
1105
1116
|
})
|
|
1106
1117
|
|
|
1107
1118
|
// Question 1: Selected 'Yes'
|
|
1108
1119
|
contextYes = model.getFormContext(requestPage1, {
|
|
1120
|
+
$$__referenceNumber: 'foobar',
|
|
1109
1121
|
yesNoField: true
|
|
1110
1122
|
})
|
|
1111
1123
|
})
|
|
@@ -1174,13 +1186,13 @@ describe('QuestionPageController V2', () => {
|
|
|
1174
1186
|
|
|
1175
1187
|
await controller1.makeGetRouteHandler()(
|
|
1176
1188
|
requestPage1,
|
|
1177
|
-
model.getFormContext(requestPage1, {}),
|
|
1189
|
+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' }),
|
|
1178
1190
|
h
|
|
1179
1191
|
)
|
|
1180
1192
|
|
|
1181
1193
|
await controller2.makeGetRouteHandler()(
|
|
1182
1194
|
requestPage2,
|
|
1183
|
-
model.getFormContext(requestPage2, {}),
|
|
1195
|
+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' }),
|
|
1184
1196
|
h
|
|
1185
1197
|
)
|
|
1186
1198
|
|
|
@@ -128,7 +128,7 @@ describe('RepeatPageController', () => {
|
|
|
128
128
|
beforeEach(() => {
|
|
129
129
|
viewModel = controller.getViewModel(
|
|
130
130
|
requestPageItem,
|
|
131
|
-
model.getFormContext(requestPageItem, {})
|
|
131
|
+
model.getFormContext(requestPageItem, { $$__referenceNumber: 'foobar' })
|
|
132
132
|
)
|
|
133
133
|
})
|
|
134
134
|
|
|
@@ -190,7 +190,9 @@ describe('RepeatPageController', () => {
|
|
|
190
190
|
beforeEach(() => {
|
|
191
191
|
viewModel = controller.getListSummaryViewModel(
|
|
192
192
|
requestPageSummary,
|
|
193
|
-
model.getFormContext(requestPageSummary, {
|
|
193
|
+
model.getFormContext(requestPageSummary, {
|
|
194
|
+
$$__referenceNumber: 'foobar'
|
|
195
|
+
}),
|
|
194
196
|
list
|
|
195
197
|
)
|
|
196
198
|
})
|
|
@@ -40,6 +40,7 @@ import { type PageController } from '~/src/server/plugins/engine/pageControllers
|
|
|
40
40
|
import { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
|
|
41
41
|
import { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
|
|
42
42
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
|
|
43
|
+
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
|
|
43
44
|
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
44
45
|
import { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'
|
|
45
46
|
import {
|
|
@@ -265,7 +266,23 @@ export const plugin = {
|
|
|
265
266
|
|
|
266
267
|
const cacheService = getCacheService(request.server)
|
|
267
268
|
const page = getPage(model, request)
|
|
268
|
-
|
|
269
|
+
let state = await page.getState(request)
|
|
270
|
+
|
|
271
|
+
if (!state.$$__referenceNumber) {
|
|
272
|
+
const prefix = model.def.metadata?.referenceNumberPrefix ?? ''
|
|
273
|
+
|
|
274
|
+
if (typeof prefix !== 'string') {
|
|
275
|
+
throw Boom.badImplementation(
|
|
276
|
+
'Reference number prefix must be a string or undefined'
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const referenceNumber = generateUniqueReference(prefix)
|
|
281
|
+
state = await page.mergeState(request, state, {
|
|
282
|
+
$$__referenceNumber: referenceNumber
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
269
286
|
const flash = cacheService.getFlash(request)
|
|
270
287
|
const context = model.getFormContext(request, state, flash?.errors)
|
|
271
288
|
const relevantPath = page.getRelevantPath(request, context)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
|
|
2
|
+
|
|
3
|
+
describe('generateUniqueReference', () => {
|
|
4
|
+
it('should generate a reference number with 3 segments when no prefix is provided', () => {
|
|
5
|
+
const referenceNumber = generateUniqueReference()
|
|
6
|
+
const segments = referenceNumber.split('-')
|
|
7
|
+
|
|
8
|
+
expect(segments).toHaveLength(3)
|
|
9
|
+
|
|
10
|
+
segments.forEach((segment) => {
|
|
11
|
+
expect(segment).toHaveLength(3)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should generate a reference number with 2 segments when a prefix is provided', () => {
|
|
16
|
+
const prefix = 'ABC'
|
|
17
|
+
const referenceNumber = generateUniqueReference(prefix)
|
|
18
|
+
const segments = referenceNumber.split('-')
|
|
19
|
+
|
|
20
|
+
expect(segments).toHaveLength(3) // 1 for prefix and 2 for segments
|
|
21
|
+
expect(segments[0]).toBe(prefix)
|
|
22
|
+
|
|
23
|
+
segments.slice(1).forEach((segment) => {
|
|
24
|
+
expect(segment).toHaveLength(3)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should generate different reference numbers on subsequent calls', () => {
|
|
29
|
+
const referenceNumber1 = generateUniqueReference()
|
|
30
|
+
const referenceNumber2 = generateUniqueReference()
|
|
31
|
+
expect(referenceNumber1).not.toBe(referenceNumber2)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.
|
|
5
|
+
* Provides no guarantee on uniqueness.
|
|
6
|
+
*/
|
|
7
|
+
export function generateUniqueReference(prefix?: string) {
|
|
8
|
+
const segmentLength = 3
|
|
9
|
+
const segmentCount = prefix ? 2 : 3
|
|
10
|
+
prefix = prefix ? `${prefix}-` : ''
|
|
11
|
+
|
|
12
|
+
const segments = Array.from(
|
|
13
|
+
{ length: segmentCount },
|
|
14
|
+
() => randomBytes(segmentLength).toString('hex').slice(0, segmentLength) // 0-9a-f, might be good enough?
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return `${prefix}${segments.join('-')}`.toUpperCase()
|
|
18
|
+
}
|