@defra/forms-engine-plugin 4.0.28 → 4.0.30
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/beta/form-context.d.ts +25 -0
- package/.server/server/plugins/engine/beta/form-context.js +122 -0
- package/.server/server/plugins/engine/beta/form-context.js.map +1 -0
- package/.server/server/plugins/engine/index.d.ts +1 -0
- package/.server/server/plugins/engine/index.js +1 -0
- package/.server/server/plugins/engine/index.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +3 -2
- package/.server/server/plugins/engine/models/FormModel.js +3 -0
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +3 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +11 -65
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/package.json +2 -2
- package/src/server/plugins/engine/beta/form-context.test.ts +359 -0
- package/src/server/plugins/engine/beta/form-context.ts +250 -0
- package/src/server/plugins/engine/index.ts +6 -0
- package/src/server/plugins/engine/models/FormModel.test.ts +70 -0
- package/src/server/plugins/engine/models/FormModel.ts +10 -3
- package/src/server/plugins/engine/pageControllers/PageController.ts +3 -3
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +7 -5
- package/src/server/plugins/engine/routes/index.ts +10 -71
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import Boom from '@hapi/boom'
|
|
2
|
+
import { type Request, type Server } from '@hapi/hapi'
|
|
3
|
+
import { isEqual } from 'date-fns'
|
|
4
|
+
|
|
5
|
+
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
|
|
6
|
+
import {
|
|
7
|
+
checkEmailAddressForLiveFormSubmission,
|
|
8
|
+
getCacheService
|
|
9
|
+
} from '~/src/server/plugins/engine/helpers.js'
|
|
10
|
+
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
11
|
+
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
12
|
+
import { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/index.js'
|
|
13
|
+
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
14
|
+
import {
|
|
15
|
+
type AnyRequest,
|
|
16
|
+
type FormContext,
|
|
17
|
+
type FormContextRequest,
|
|
18
|
+
type FormSubmissionError,
|
|
19
|
+
type FormSubmissionState
|
|
20
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
21
|
+
import { FormStatus } from '~/src/server/routes/types.js'
|
|
22
|
+
import { type Services } from '~/src/server/types.js'
|
|
23
|
+
|
|
24
|
+
type JourneyState = FormStatus | 'preview'
|
|
25
|
+
|
|
26
|
+
export interface FormModelOptions {
|
|
27
|
+
services?: Services
|
|
28
|
+
controllers?: Record<string, typeof PageController>
|
|
29
|
+
basePath?: string
|
|
30
|
+
versionNumber?: number
|
|
31
|
+
ordnanceSurveyApiKey?: string
|
|
32
|
+
formId?: string
|
|
33
|
+
routePrefix?: string
|
|
34
|
+
isPreview?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FormContextOptions extends FormModelOptions {
|
|
38
|
+
errors?: FormSubmissionError[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type SummaryRequest = FormContextRequest & {
|
|
42
|
+
yar: Request['yar']
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function getFormModel(
|
|
46
|
+
slug: string,
|
|
47
|
+
state: JourneyState,
|
|
48
|
+
options: FormModelOptions = {}
|
|
49
|
+
) {
|
|
50
|
+
const services = options.services ?? defaultServices
|
|
51
|
+
const { formsService } = services
|
|
52
|
+
const isPreview = isPreviewState(state, options)
|
|
53
|
+
const formState = resolveState(state)
|
|
54
|
+
|
|
55
|
+
const metadata = await formsService.getFormMetadata(slug)
|
|
56
|
+
const versionNumber =
|
|
57
|
+
options.versionNumber ?? metadata.versions?.[0]?.versionNumber
|
|
58
|
+
|
|
59
|
+
const definition = await formsService.getFormDefinition(
|
|
60
|
+
metadata.id,
|
|
61
|
+
formState
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if (!definition) {
|
|
65
|
+
throw Boom.notFound(
|
|
66
|
+
`No definition found for form metadata ${metadata.id} (${slug}) ${state}`
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new FormModel(
|
|
71
|
+
definition,
|
|
72
|
+
{
|
|
73
|
+
basePath:
|
|
74
|
+
options.basePath ??
|
|
75
|
+
buildBasePath(options.routePrefix ?? '', slug, formState, isPreview),
|
|
76
|
+
versionNumber,
|
|
77
|
+
ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,
|
|
78
|
+
formId: options.formId ?? metadata.id
|
|
79
|
+
},
|
|
80
|
+
services,
|
|
81
|
+
options.controllers
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function getFormContext(
|
|
86
|
+
{ server, yar }: Pick<Request, 'server' | 'yar'>,
|
|
87
|
+
slug: string,
|
|
88
|
+
state: JourneyState = FormStatus.Live,
|
|
89
|
+
options: FormContextOptions = {}
|
|
90
|
+
): Promise<FormContext> {
|
|
91
|
+
const formModel = await resolveFormModel(server, slug, state, options)
|
|
92
|
+
|
|
93
|
+
const cacheService = getCacheService(server)
|
|
94
|
+
|
|
95
|
+
const summaryRequest: SummaryRequest = {
|
|
96
|
+
app: {},
|
|
97
|
+
method: 'get',
|
|
98
|
+
params: {
|
|
99
|
+
path: 'summary',
|
|
100
|
+
slug,
|
|
101
|
+
...(isPreviewState(state, options) && {
|
|
102
|
+
state: resolveState(state)
|
|
103
|
+
})
|
|
104
|
+
},
|
|
105
|
+
path: `/${formModel.basePath}/summary`,
|
|
106
|
+
query: {},
|
|
107
|
+
url: new URL(
|
|
108
|
+
`/${formModel.basePath}/summary`,
|
|
109
|
+
'https://form-context.local'
|
|
110
|
+
),
|
|
111
|
+
server,
|
|
112
|
+
yar
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const cachedState = await cacheService.getState(
|
|
116
|
+
summaryRequest as unknown as AnyRequest
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
const formState = {
|
|
120
|
+
...cachedState,
|
|
121
|
+
$$__referenceNumber: cachedState.$$__referenceNumber
|
|
122
|
+
} as unknown as FormSubmissionState
|
|
123
|
+
|
|
124
|
+
return formModel.getFormContext(
|
|
125
|
+
summaryRequest,
|
|
126
|
+
formState,
|
|
127
|
+
options.errors ?? []
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function resolveFormModel(
|
|
132
|
+
server: Server,
|
|
133
|
+
slug: string,
|
|
134
|
+
state: JourneyState,
|
|
135
|
+
options: FormModelOptions = {}
|
|
136
|
+
) {
|
|
137
|
+
const services = options.services ?? defaultServices
|
|
138
|
+
const { formsService } = services
|
|
139
|
+
|
|
140
|
+
const metadata = await formsService.getFormMetadata(slug)
|
|
141
|
+
const formState = resolveState(state)
|
|
142
|
+
const isPreview = options.isPreview ?? isPreviewState(state, options)
|
|
143
|
+
const stateMetadata = metadata[formState]
|
|
144
|
+
|
|
145
|
+
if (!stateMetadata) {
|
|
146
|
+
throw Boom.notFound(
|
|
147
|
+
`No '${formState}' state for form metadata ${metadata.id}`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// The models cache is created lazily per server instance
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
153
|
+
if (!server.app.models) {
|
|
154
|
+
server.app.models = new Map<string, { model: FormModel; updatedAt: Date }>()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const cache = server.app.models as Map<
|
|
158
|
+
string,
|
|
159
|
+
{ model: FormModel; updatedAt: Date }
|
|
160
|
+
>
|
|
161
|
+
|
|
162
|
+
const cacheKey = `${metadata.id}_${formState}_${isPreview}`
|
|
163
|
+
let entry = cache.get(cacheKey)
|
|
164
|
+
|
|
165
|
+
if (!entry || !isEqual(entry.updatedAt, stateMetadata.updatedAt)) {
|
|
166
|
+
const definition = await formsService.getFormDefinition(
|
|
167
|
+
metadata.id,
|
|
168
|
+
formState
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if (!definition) {
|
|
172
|
+
throw Boom.notFound(
|
|
173
|
+
`No definition found for form metadata ${metadata.id} (${slug}) ${state}`
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const emailAddress = metadata.notificationEmail ?? definition.outputEmail
|
|
178
|
+
|
|
179
|
+
checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)
|
|
180
|
+
|
|
181
|
+
const routePrefix =
|
|
182
|
+
options.routePrefix ?? server.realm.modifiers.route.prefix
|
|
183
|
+
|
|
184
|
+
const model = new FormModel(
|
|
185
|
+
definition,
|
|
186
|
+
{
|
|
187
|
+
basePath:
|
|
188
|
+
options.basePath ??
|
|
189
|
+
buildBasePath(routePrefix, slug, formState, isPreview),
|
|
190
|
+
versionNumber:
|
|
191
|
+
options.versionNumber ?? metadata.versions?.[0]?.versionNumber,
|
|
192
|
+
ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,
|
|
193
|
+
formId: options.formId ?? metadata.id
|
|
194
|
+
},
|
|
195
|
+
services,
|
|
196
|
+
options.controllers
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
entry = { model, updatedAt: stateMetadata.updatedAt }
|
|
200
|
+
cache.set(cacheKey, entry)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return entry.model
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildBasePath(
|
|
207
|
+
routePrefix: string,
|
|
208
|
+
slug: string,
|
|
209
|
+
state: FormStatus,
|
|
210
|
+
isPreview: boolean
|
|
211
|
+
) {
|
|
212
|
+
const base = (
|
|
213
|
+
isPreview
|
|
214
|
+
? `${routePrefix}${PREVIEW_PATH_PREFIX}/${state}/${slug}`
|
|
215
|
+
: `${routePrefix}/${slug}`
|
|
216
|
+
).replace(/\/{2,}/g, '/')
|
|
217
|
+
|
|
218
|
+
return base.startsWith('/') ? base.slice(1) : base
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function getFirstJourneyPage(
|
|
222
|
+
context?: Pick<FormContext, 'relevantPages'>
|
|
223
|
+
) {
|
|
224
|
+
if (!context?.relevantPages) {
|
|
225
|
+
return undefined
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const lastPageReached = context.relevantPages.at(-1)
|
|
229
|
+
const penultimatePageReached = context.relevantPages.at(-2)
|
|
230
|
+
|
|
231
|
+
if (
|
|
232
|
+
lastPageReached instanceof TerminalPageController &&
|
|
233
|
+
penultimatePageReached
|
|
234
|
+
) {
|
|
235
|
+
return penultimatePageReached
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return lastPageReached
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resolveState(state: JourneyState): FormStatus {
|
|
242
|
+
return state === 'preview' ? FormStatus.Live : state
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isPreviewState(
|
|
246
|
+
state: JourneyState,
|
|
247
|
+
options: FormModelOptions = {}
|
|
248
|
+
): boolean {
|
|
249
|
+
return options.isPreview ?? state === 'preview'
|
|
250
|
+
}
|
|
@@ -14,6 +14,12 @@ import * as filters from '~/src/server/plugins/nunjucks/filters/index.js'
|
|
|
14
14
|
|
|
15
15
|
export { getPageHref } from '~/src/server/plugins/engine/helpers.js'
|
|
16
16
|
export { context } from '~/src/server/plugins/nunjucks/context.js'
|
|
17
|
+
export {
|
|
18
|
+
getFirstJourneyPage,
|
|
19
|
+
getFormContext,
|
|
20
|
+
getFormModel,
|
|
21
|
+
resolveFormModel
|
|
22
|
+
} from '~/src/server/plugins/engine/beta/form-context.js'
|
|
17
23
|
|
|
18
24
|
const globals = {
|
|
19
25
|
checkComponentTemplates,
|
|
@@ -716,4 +716,74 @@ describe('FormModel - Joined Conditions', () => {
|
|
|
716
716
|
expect(Object.keys(model.conditions)).toHaveLength(3)
|
|
717
717
|
})
|
|
718
718
|
})
|
|
719
|
+
|
|
720
|
+
describe('getSection', () => {
|
|
721
|
+
it('should look up section by name for V1 schema', () => {
|
|
722
|
+
const v1Definition = {
|
|
723
|
+
...definition,
|
|
724
|
+
sections: [
|
|
725
|
+
{ name: 'personal', title: 'Personal details' },
|
|
726
|
+
{ name: 'contact', title: 'Contact details' }
|
|
727
|
+
]
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const model = new FormModel(v1Definition, { basePath: 'test' })
|
|
731
|
+
|
|
732
|
+
expect(model.getSection('personal')).toEqual(
|
|
733
|
+
expect.objectContaining({
|
|
734
|
+
name: 'personal',
|
|
735
|
+
title: 'Personal details'
|
|
736
|
+
})
|
|
737
|
+
)
|
|
738
|
+
expect(model.getSection('contact')).toEqual(
|
|
739
|
+
expect.objectContaining({
|
|
740
|
+
name: 'contact',
|
|
741
|
+
title: 'Contact details'
|
|
742
|
+
})
|
|
743
|
+
)
|
|
744
|
+
expect(model.getSection('nonexistent')).toBeUndefined()
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
it('should look up section by ID for V2 schema', () => {
|
|
748
|
+
const v2Definition = {
|
|
749
|
+
...definitionV2,
|
|
750
|
+
sections: [
|
|
751
|
+
{
|
|
752
|
+
id: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
|
|
753
|
+
name: 'personal',
|
|
754
|
+
title: 'Personal details'
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
id: 'b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e',
|
|
758
|
+
name: 'contact',
|
|
759
|
+
title: 'Contact details'
|
|
760
|
+
}
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
formDefinitionV2Schema.validate = jest
|
|
765
|
+
.fn()
|
|
766
|
+
.mockReturnValue({ value: v2Definition })
|
|
767
|
+
|
|
768
|
+
const model = new FormModel(v2Definition, { basePath: 'test' })
|
|
769
|
+
|
|
770
|
+
expect(model.getSection('a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d')).toEqual(
|
|
771
|
+
expect.objectContaining({
|
|
772
|
+
id: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
|
|
773
|
+
name: 'personal',
|
|
774
|
+
title: 'Personal details'
|
|
775
|
+
})
|
|
776
|
+
)
|
|
777
|
+
expect(model.getSection('b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e')).toEqual(
|
|
778
|
+
expect.objectContaining({
|
|
779
|
+
id: 'b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e',
|
|
780
|
+
name: 'contact',
|
|
781
|
+
title: 'Contact details'
|
|
782
|
+
})
|
|
783
|
+
)
|
|
784
|
+
// V2 should not find by name
|
|
785
|
+
expect(model.getSection('personal')).toBeUndefined()
|
|
786
|
+
expect(model.getSection('nonexistent')).toBeUndefined()
|
|
787
|
+
})
|
|
788
|
+
})
|
|
719
789
|
})
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
type Engine,
|
|
22
22
|
type FormDefinition,
|
|
23
23
|
type List,
|
|
24
|
-
type Page
|
|
24
|
+
type Page,
|
|
25
|
+
type Section
|
|
25
26
|
} from '@defra/forms-model'
|
|
26
27
|
import { add, format } from 'date-fns'
|
|
27
28
|
import { Parser, type Value } from 'expr-eval-fork'
|
|
@@ -321,12 +322,18 @@ export class FormModel {
|
|
|
321
322
|
: this.lists.find((list) => list.id === nameOrId)
|
|
322
323
|
}
|
|
323
324
|
|
|
325
|
+
getSection(nameOrId: string): Section | undefined {
|
|
326
|
+
return this.schemaVersion === SchemaVersion.V1
|
|
327
|
+
? this.sections.find((section) => section.name === nameOrId)
|
|
328
|
+
: this.sections.find((section) => section.id === nameOrId)
|
|
329
|
+
}
|
|
330
|
+
|
|
324
331
|
/**
|
|
325
332
|
* Form context for the current page
|
|
326
333
|
*/
|
|
327
334
|
getFormContext(
|
|
328
335
|
request: FormContextRequest,
|
|
329
|
-
state:
|
|
336
|
+
state: FormSubmissionState,
|
|
330
337
|
errors?: FormSubmissionError[]
|
|
331
338
|
): FormContext {
|
|
332
339
|
const { query } = request
|
|
@@ -625,7 +632,7 @@ function validateFormState(
|
|
|
625
632
|
return context
|
|
626
633
|
}
|
|
627
634
|
|
|
628
|
-
function getReferenceNumber(state:
|
|
635
|
+
function getReferenceNumber(state: FormSubmissionState): string {
|
|
629
636
|
if (
|
|
630
637
|
!state.$$__referenceNumber ||
|
|
631
638
|
typeof state.$$__referenceNumber !== 'string'
|
|
@@ -55,9 +55,9 @@ export class PageController {
|
|
|
55
55
|
this.events = pageDef.events
|
|
56
56
|
|
|
57
57
|
// Resolve section
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if (pageDef.section) {
|
|
59
|
+
this.section = model.getSection(pageDef.section)
|
|
60
|
+
}
|
|
61
61
|
|
|
62
62
|
// Resolve condition
|
|
63
63
|
if (pageDef.condition) {
|
|
@@ -1007,11 +1007,13 @@ describe('QuestionPageController V2', () => {
|
|
|
1007
1007
|
|
|
1008
1008
|
it('returns the page section', () => {
|
|
1009
1009
|
expect(controller1).toHaveProperty('section', undefined)
|
|
1010
|
-
expect(controller2).
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1010
|
+
expect(controller2.section).toEqual(
|
|
1011
|
+
expect.objectContaining({
|
|
1012
|
+
name: 'marriage',
|
|
1013
|
+
title: 'Your marriage',
|
|
1014
|
+
hideTitle: false
|
|
1015
|
+
})
|
|
1016
|
+
)
|
|
1015
1017
|
})
|
|
1016
1018
|
})
|
|
1017
1019
|
|
|
@@ -4,19 +4,17 @@ import {
|
|
|
4
4
|
type ResponseToolkit,
|
|
5
5
|
type Server
|
|
6
6
|
} from '@hapi/hapi'
|
|
7
|
-
import { isEqual } from 'date-fns'
|
|
8
7
|
|
|
9
8
|
import {
|
|
10
9
|
EXTERNAL_STATE_APPENDAGE,
|
|
11
|
-
EXTERNAL_STATE_PAYLOAD
|
|
12
|
-
PREVIEW_PATH_PREFIX
|
|
10
|
+
EXTERNAL_STATE_PAYLOAD
|
|
13
11
|
} from '~/src/server/constants.js'
|
|
12
|
+
import { resolveFormModel } from '~/src/server/plugins/engine/beta/form-context.js'
|
|
14
13
|
import {
|
|
15
14
|
FormComponent,
|
|
16
15
|
isFormState
|
|
17
16
|
} from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
18
17
|
import {
|
|
19
|
-
checkEmailAddressForLiveFormSubmission,
|
|
20
18
|
checkFormStatus,
|
|
21
19
|
findPage,
|
|
22
20
|
getCacheService,
|
|
@@ -24,7 +22,6 @@ import {
|
|
|
24
22
|
getStartPath,
|
|
25
23
|
proceed
|
|
26
24
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
27
|
-
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
28
25
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
29
26
|
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
|
|
30
27
|
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
@@ -179,8 +176,6 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
|
|
|
179
176
|
ordnanceSurveyApiKey
|
|
180
177
|
} = options
|
|
181
178
|
|
|
182
|
-
const { formsService } = services
|
|
183
|
-
|
|
184
179
|
async function handler(request: AnyFormRequest, h: ResponseToolkit) {
|
|
185
180
|
if (server.app.model) {
|
|
186
181
|
request.app.model = server.app.model
|
|
@@ -192,71 +187,15 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
|
|
|
192
187
|
const { slug } = params
|
|
193
188
|
const { isPreview, state: formState } = checkFormStatus(params)
|
|
194
189
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Cache the models based on id, state and whether
|
|
206
|
-
// it's a preview or not. There could be up to 3 models
|
|
207
|
-
// cached for a single form:
|
|
208
|
-
// "{id}_live_false" (live/live)
|
|
209
|
-
// "{id}_live_true" (live/preview)
|
|
210
|
-
// "{id}_draft_true" (draft/preview)
|
|
211
|
-
const key = `${id}_${formState}_${isPreview}`
|
|
212
|
-
let item = server.app.models.get(key)
|
|
213
|
-
|
|
214
|
-
if (!item || !isEqual(item.updatedAt, state.updatedAt)) {
|
|
215
|
-
server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)
|
|
216
|
-
|
|
217
|
-
// Get the form definition using the `id` from the metadata
|
|
218
|
-
const definition = await formsService.getFormDefinition(id, formState)
|
|
219
|
-
|
|
220
|
-
if (!definition) {
|
|
221
|
-
throw Boom.notFound(
|
|
222
|
-
`No definition found for form metadata ${id} (${slug}) ${formState}`
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const emailAddress = metadata.notificationEmail ?? definition.outputEmail
|
|
227
|
-
|
|
228
|
-
checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)
|
|
229
|
-
|
|
230
|
-
// Build the form model
|
|
231
|
-
server.logger.info(
|
|
232
|
-
`Building model for form definition ${id} (${slug}) ${formState}`
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
// Set up the basePath for the model
|
|
236
|
-
const basePath = (
|
|
237
|
-
isPreview
|
|
238
|
-
? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`
|
|
239
|
-
: `${prefix}/${slug}`
|
|
240
|
-
).substring(1)
|
|
241
|
-
|
|
242
|
-
const versionNumber = metadata.versions?.[0]?.versionNumber
|
|
243
|
-
|
|
244
|
-
// Construct the form model
|
|
245
|
-
const model = new FormModel(
|
|
246
|
-
definition,
|
|
247
|
-
{ basePath, versionNumber, ordnanceSurveyApiKey, formId: id },
|
|
248
|
-
services,
|
|
249
|
-
controllers
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
// Create new item and add it to the item cache
|
|
253
|
-
item = { model, updatedAt: state.updatedAt }
|
|
254
|
-
server.app.models.set(key, item)
|
|
255
|
-
}
|
|
190
|
+
const model = await resolveFormModel(server, slug, formState, {
|
|
191
|
+
services,
|
|
192
|
+
controllers,
|
|
193
|
+
ordnanceSurveyApiKey,
|
|
194
|
+
routePrefix: prefix,
|
|
195
|
+
isPreview
|
|
196
|
+
})
|
|
256
197
|
|
|
257
|
-
|
|
258
|
-
// for use in the downstream handler
|
|
259
|
-
request.app.model = item.model
|
|
198
|
+
request.app.model = model
|
|
260
199
|
|
|
261
200
|
return h.continue
|
|
262
201
|
}
|