@defra/forms-engine-plugin 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/forms/page-events.yaml +87 -0
- package/.server/server/index.js +2 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.d.ts +4 -2
- package/.server/server/plugins/engine/routes/questions.js +67 -43
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.js +7 -9
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
- package/.server/server/routes/dummy-api.d.ts +38 -0
- package/.server/server/routes/dummy-api.js +33 -0
- package/.server/server/routes/dummy-api.js.map +1 -0
- package/.server/server/routes/index.d.ts +1 -0
- package/.server/server/routes/index.js +1 -0
- package/.server/server/routes/index.js.map +1 -1
- package/package.json +3 -1
- package/src/server/forms/page-events.yaml +87 -0
- package/src/server/index.ts +4 -2
- package/src/server/plugins/engine/routes/questions.test.ts +416 -0
- package/src/server/plugins/engine/routes/questions.ts +96 -40
- package/src/server/plugins/engine/services/localFormsService.js +7 -8
- package/src/server/routes/dummy-api.test.ts +96 -0
- package/src/server/routes/dummy-api.ts +62 -0
- package/src/server/routes/index.ts +1 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.json +0 -393
- package/src/server/forms/register-as-a-unicorn-breeder.json +0 -393
package/src/server/index.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { Engine as CatboxRedis } from '@hapi/catbox-redis'
|
|
|
3
3
|
import hapi, {
|
|
4
4
|
type Request,
|
|
5
5
|
type ResponseToolkit,
|
|
6
|
-
type ServerOptions
|
|
6
|
+
type ServerOptions,
|
|
7
|
+
type ServerRoute
|
|
7
8
|
} from '@hapi/hapi'
|
|
8
9
|
import inert from '@hapi/inert'
|
|
9
10
|
import Scooter from '@hapi/scooter'
|
|
@@ -21,7 +22,7 @@ import pluginErrorPages from '~/src/server/plugins/errorPages.js'
|
|
|
21
22
|
import { plugin as pluginViews } from '~/src/server/plugins/nunjucks/index.js'
|
|
22
23
|
import pluginPulse from '~/src/server/plugins/pulse.js'
|
|
23
24
|
import pluginSession from '~/src/server/plugins/session.js'
|
|
24
|
-
import { publicRoutes } from '~/src/server/routes/index.js'
|
|
25
|
+
import { dummyApiRoutes, publicRoutes } from '~/src/server/routes/index.js'
|
|
25
26
|
import { prepareSecureContext } from '~/src/server/secure-context.js'
|
|
26
27
|
import { type RouteConfig } from '~/src/server/types.js'
|
|
27
28
|
|
|
@@ -120,6 +121,7 @@ export async function createServer(routeConfig?: RouteConfig) {
|
|
|
120
121
|
name: 'router',
|
|
121
122
|
register: (server) => {
|
|
122
123
|
server.route(publicRoutes)
|
|
124
|
+
server.route(dummyApiRoutes as ServerRoute[])
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
})
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import Boom from '@hapi/boom'
|
|
2
|
+
import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'
|
|
3
|
+
// eslint-disable-next-line n/no-unpublished-import
|
|
4
|
+
import nock from 'nock'
|
|
5
|
+
|
|
6
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
7
|
+
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
|
|
8
|
+
import { redirectOrMakeHandler } from '~/src/server/plugins/engine/routes/index.js'
|
|
9
|
+
import {
|
|
10
|
+
makeGetHandler,
|
|
11
|
+
makePostHandler
|
|
12
|
+
} from '~/src/server/plugins/engine/routes/questions.js'
|
|
13
|
+
import { type FormContext } from '~/src/server/plugins/engine/types.js'
|
|
14
|
+
import {
|
|
15
|
+
type FormRequest,
|
|
16
|
+
type FormRequestPayload
|
|
17
|
+
} from '~/src/server/routes/types.js'
|
|
18
|
+
jest.mock('~/src/server/plugins/engine/models/SummaryViewModel', () => ({
|
|
19
|
+
SummaryViewModel: class {
|
|
20
|
+
summary = 'mocked summary'
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
jest.mock(
|
|
25
|
+
'~/src/server/plugins/engine/pageControllers/SummaryPageController',
|
|
26
|
+
() => ({
|
|
27
|
+
getFormSubmissionData: jest.fn().mockReturnValue([])
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
jest.mock('~/src/server/plugins/engine/outputFormatters/machine/v1', () => ({
|
|
32
|
+
format: jest.fn().mockReturnValue('mocked format')
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
jest.mock('~/src/server/plugins/engine/routes/index')
|
|
36
|
+
|
|
37
|
+
describe('makeGetHandler', () => {
|
|
38
|
+
const hMock: Pick<ResponseToolkit, 'redirect' | 'view'> = {
|
|
39
|
+
redirect: jest.fn(),
|
|
40
|
+
view: jest.fn()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
nock('http://test').persist().post('/load').reply(200, {
|
|
45
|
+
wasGetCalled: true
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.mocked(redirectOrMakeHandler).mockRestore()
|
|
51
|
+
nock.cleanAll()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('calls the callback when events.onLoad.type is http', async () => {
|
|
55
|
+
let data = {}
|
|
56
|
+
|
|
57
|
+
const modelMock = {
|
|
58
|
+
basePath: 'some-base-path',
|
|
59
|
+
def: { name: 'Hello world' }
|
|
60
|
+
} as FormModel
|
|
61
|
+
|
|
62
|
+
const pageMock = createMockPageController(
|
|
63
|
+
modelMock,
|
|
64
|
+
(
|
|
65
|
+
_request: FormRequest,
|
|
66
|
+
context: FormContext,
|
|
67
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
68
|
+
) => {
|
|
69
|
+
data = context.data
|
|
70
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
75
|
+
|
|
76
|
+
const requestMock = {
|
|
77
|
+
params: { path: 'some-path' },
|
|
78
|
+
app: { model: modelMock }
|
|
79
|
+
} as FormRequest
|
|
80
|
+
|
|
81
|
+
jest
|
|
82
|
+
.mocked(redirectOrMakeHandler)
|
|
83
|
+
.mockImplementation(
|
|
84
|
+
(
|
|
85
|
+
_req: FormRequest | FormRequestPayload,
|
|
86
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
87
|
+
fn
|
|
88
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await makeGetHandler()(requestMock, hMock)
|
|
92
|
+
|
|
93
|
+
expect(data).toMatchObject({
|
|
94
|
+
wasGetCalled: true
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('does not call the callback when the events.onLoad.type is not http', async () => {
|
|
99
|
+
let data = {}
|
|
100
|
+
|
|
101
|
+
const modelMock = {
|
|
102
|
+
basePath: 'some-base-path',
|
|
103
|
+
def: { name: 'Hello world' }
|
|
104
|
+
} as FormModel
|
|
105
|
+
|
|
106
|
+
const pageMock = createMockPageController(
|
|
107
|
+
modelMock,
|
|
108
|
+
(
|
|
109
|
+
_request: FormRequest,
|
|
110
|
+
context: FormContext,
|
|
111
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
112
|
+
) => {
|
|
113
|
+
data = context.data
|
|
114
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
pageMock.events = {}
|
|
119
|
+
|
|
120
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
121
|
+
|
|
122
|
+
const requestMock = {
|
|
123
|
+
params: { path: 'some-path' },
|
|
124
|
+
app: { model: modelMock }
|
|
125
|
+
} as FormRequest
|
|
126
|
+
|
|
127
|
+
jest
|
|
128
|
+
.mocked(redirectOrMakeHandler)
|
|
129
|
+
.mockImplementation(
|
|
130
|
+
(
|
|
131
|
+
_req: FormRequest | FormRequestPayload,
|
|
132
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
133
|
+
fn
|
|
134
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
await makeGetHandler()(requestMock, hMock)
|
|
138
|
+
|
|
139
|
+
expect(data).toMatchObject({})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('throws when model is missing', async () => {
|
|
143
|
+
let error
|
|
144
|
+
|
|
145
|
+
const modelMock = {
|
|
146
|
+
basePath: 'some-base-path',
|
|
147
|
+
def: { name: 'Hello world' }
|
|
148
|
+
} as FormModel
|
|
149
|
+
|
|
150
|
+
const pageMock = createMockPageController(
|
|
151
|
+
modelMock,
|
|
152
|
+
(
|
|
153
|
+
_request: FormRequest,
|
|
154
|
+
_context: FormContext,
|
|
155
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
156
|
+
) => {
|
|
157
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
162
|
+
|
|
163
|
+
const requestMock = {
|
|
164
|
+
params: { path: 'some-path' },
|
|
165
|
+
app: {}
|
|
166
|
+
} as FormRequest
|
|
167
|
+
|
|
168
|
+
jest
|
|
169
|
+
.mocked(redirectOrMakeHandler)
|
|
170
|
+
.mockImplementation(
|
|
171
|
+
async (
|
|
172
|
+
_req: FormRequest | FormRequestPayload,
|
|
173
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
174
|
+
fn
|
|
175
|
+
) => {
|
|
176
|
+
try {
|
|
177
|
+
await fn(pageMock, contextMock)
|
|
178
|
+
} catch (err) {
|
|
179
|
+
error = err
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
await makeGetHandler()(requestMock, hMock)
|
|
187
|
+
|
|
188
|
+
expect(error).toEqual(Boom.notFound('No model found for /some-path'))
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('makePostHandler', () => {
|
|
193
|
+
const hMock: Pick<ResponseToolkit, 'redirect' | 'view'> = {
|
|
194
|
+
redirect: jest.fn(),
|
|
195
|
+
view: jest.fn()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
nock('http://test').post('/save').reply(200, {
|
|
200
|
+
wasPostCalled: true
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
afterEach(() => {
|
|
205
|
+
jest.mocked(redirectOrMakeHandler).mockRestore()
|
|
206
|
+
nock.cleanAll()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('calls the callback when events.onSave.type is http and the page controller was successful', async () => {
|
|
210
|
+
const mockPostResponse: ResponseObject = {
|
|
211
|
+
statusCode: 200
|
|
212
|
+
} as ResponseObject
|
|
213
|
+
|
|
214
|
+
const modelMock = {
|
|
215
|
+
basePath: 'some-base-path',
|
|
216
|
+
def: { name: 'Hello world' }
|
|
217
|
+
} as FormModel
|
|
218
|
+
|
|
219
|
+
const pageMock = createMockPageController(
|
|
220
|
+
modelMock,
|
|
221
|
+
(
|
|
222
|
+
_request: FormRequest,
|
|
223
|
+
_context: FormContext,
|
|
224
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
225
|
+
) => {
|
|
226
|
+
// do return a valid ResponseObject wrapped in Promise.resolve
|
|
227
|
+
return mockPostResponse
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
232
|
+
|
|
233
|
+
const requestMock = {
|
|
234
|
+
params: { path: 'some-path' },
|
|
235
|
+
app: { model: modelMock },
|
|
236
|
+
payload: { some: 'payload' }
|
|
237
|
+
} as unknown as FormRequestPayload
|
|
238
|
+
|
|
239
|
+
jest
|
|
240
|
+
.mocked(redirectOrMakeHandler)
|
|
241
|
+
.mockImplementation(
|
|
242
|
+
(
|
|
243
|
+
_req: FormRequest | FormRequestPayload,
|
|
244
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
245
|
+
fn
|
|
246
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
const response = await makePostHandler()(requestMock, hMock)
|
|
250
|
+
|
|
251
|
+
expect(nock.pendingMocks()).toBeEmpty()
|
|
252
|
+
expect(response).toBe(mockPostResponse)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('does not call the callback when the events.onSave.type is not http', async () => {
|
|
256
|
+
const modelMock = {
|
|
257
|
+
basePath: 'some-base-path',
|
|
258
|
+
def: { name: 'Hello world' }
|
|
259
|
+
} as FormModel
|
|
260
|
+
|
|
261
|
+
const pageMock = createMockPageController(
|
|
262
|
+
modelMock,
|
|
263
|
+
(
|
|
264
|
+
_request: FormRequest,
|
|
265
|
+
_context: FormContext,
|
|
266
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
267
|
+
) => {
|
|
268
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
pageMock.events = {}
|
|
273
|
+
|
|
274
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
275
|
+
|
|
276
|
+
const requestMock = {
|
|
277
|
+
params: { path: 'some-path' },
|
|
278
|
+
app: { model: modelMock },
|
|
279
|
+
payload: { some: 'payload' }
|
|
280
|
+
} as unknown as FormRequestPayload
|
|
281
|
+
|
|
282
|
+
jest
|
|
283
|
+
.mocked(redirectOrMakeHandler)
|
|
284
|
+
.mockImplementation(
|
|
285
|
+
(
|
|
286
|
+
_req: FormRequest | FormRequestPayload,
|
|
287
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
288
|
+
fn
|
|
289
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
await makePostHandler()(requestMock, hMock)
|
|
293
|
+
|
|
294
|
+
expect(nock.pendingMocks()).not.toBeEmpty()
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('does not call the callback when events.onSave.type is http and the page controller was unsuccessful', async () => {
|
|
298
|
+
const mockPostResponse: ResponseObject = {
|
|
299
|
+
statusCode: 500
|
|
300
|
+
} as ResponseObject
|
|
301
|
+
|
|
302
|
+
const modelMock = {
|
|
303
|
+
basePath: 'some-base-path',
|
|
304
|
+
def: { name: 'Hello world' }
|
|
305
|
+
} as FormModel
|
|
306
|
+
|
|
307
|
+
const pageMock = createMockPageController(
|
|
308
|
+
modelMock,
|
|
309
|
+
(
|
|
310
|
+
_request: FormRequest,
|
|
311
|
+
_context: FormContext,
|
|
312
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
313
|
+
) => {
|
|
314
|
+
// do return a valid ResponseObject wrapped in Promise.resolve
|
|
315
|
+
return mockPostResponse
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
320
|
+
|
|
321
|
+
const requestMock = {
|
|
322
|
+
params: { path: 'some-path' },
|
|
323
|
+
app: { model: modelMock },
|
|
324
|
+
payload: { some: 'payload' }
|
|
325
|
+
} as unknown as FormRequestPayload
|
|
326
|
+
|
|
327
|
+
jest
|
|
328
|
+
.mocked(redirectOrMakeHandler)
|
|
329
|
+
.mockImplementation(
|
|
330
|
+
(
|
|
331
|
+
_req: FormRequest | FormRequestPayload,
|
|
332
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
333
|
+
fn
|
|
334
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
await makePostHandler()(requestMock, hMock)
|
|
338
|
+
|
|
339
|
+
expect(nock.pendingMocks()).not.toBeEmpty()
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('throws when model is missing', async () => {
|
|
343
|
+
let error
|
|
344
|
+
|
|
345
|
+
const modelMock = {
|
|
346
|
+
basePath: 'some-base-path',
|
|
347
|
+
def: { name: 'Hello world' }
|
|
348
|
+
} as FormModel
|
|
349
|
+
|
|
350
|
+
const pageMock = createMockPageController(
|
|
351
|
+
modelMock,
|
|
352
|
+
(
|
|
353
|
+
_request: FormRequest,
|
|
354
|
+
_context: FormContext,
|
|
355
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
356
|
+
) => {
|
|
357
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
362
|
+
|
|
363
|
+
const requestMock = {
|
|
364
|
+
params: { path: 'some-path' },
|
|
365
|
+
app: {},
|
|
366
|
+
payload: { some: 'payload' }
|
|
367
|
+
} as unknown as FormRequestPayload
|
|
368
|
+
|
|
369
|
+
jest
|
|
370
|
+
.mocked(redirectOrMakeHandler)
|
|
371
|
+
.mockImplementation(
|
|
372
|
+
async (
|
|
373
|
+
_req: FormRequest | FormRequestPayload,
|
|
374
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
375
|
+
fn
|
|
376
|
+
) => {
|
|
377
|
+
try {
|
|
378
|
+
await fn(pageMock, contextMock)
|
|
379
|
+
} catch (err) {
|
|
380
|
+
error = err
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
await makePostHandler()(requestMock, hMock)
|
|
388
|
+
|
|
389
|
+
expect(error).toEqual(Boom.notFound('No model found for /some-path'))
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
function createMockPageController(
|
|
394
|
+
model: FormModel,
|
|
395
|
+
routeHandler: (
|
|
396
|
+
request: FormRequest,
|
|
397
|
+
context: FormContext,
|
|
398
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
399
|
+
) => ResponseObject | Promise<ResponseObject>
|
|
400
|
+
): PageControllerClass {
|
|
401
|
+
return {
|
|
402
|
+
model,
|
|
403
|
+
events: {
|
|
404
|
+
onLoad: {
|
|
405
|
+
type: 'http',
|
|
406
|
+
options: { method: 'POST', url: 'http://test/load' }
|
|
407
|
+
},
|
|
408
|
+
onSave: {
|
|
409
|
+
type: 'http',
|
|
410
|
+
options: { method: 'POST', url: 'http://test/save' }
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
makeGetRouteHandler: () => routeHandler,
|
|
414
|
+
makePostRouteHandler: () => routeHandler
|
|
415
|
+
} as unknown as PageControllerClass
|
|
416
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { hasFormComponents, slugSchema } from '@defra/forms-model'
|
|
1
|
+
import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'
|
|
2
2
|
import Boom from '@hapi/boom'
|
|
3
3
|
import {
|
|
4
|
+
type ResponseObject,
|
|
4
5
|
type ResponseToolkit,
|
|
5
6
|
type RouteOptions,
|
|
6
7
|
type ServerRoute
|
|
@@ -12,14 +13,21 @@ import {
|
|
|
12
13
|
proceed,
|
|
13
14
|
redirectPath
|
|
14
15
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
SummaryViewModel,
|
|
18
|
+
type FormModel
|
|
19
|
+
} from '~/src/server/plugins/engine/models/index.js'
|
|
16
20
|
import { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'
|
|
17
21
|
import { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
|
|
22
|
+
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
|
|
18
23
|
import {
|
|
19
24
|
dispatchHandler,
|
|
20
25
|
redirectOrMakeHandler
|
|
21
26
|
} from '~/src/server/plugins/engine/routes/index.js'
|
|
22
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
type FormContext,
|
|
29
|
+
type PreparePageEventRequestOptions
|
|
30
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
23
31
|
import {
|
|
24
32
|
type FormRequest,
|
|
25
33
|
type FormRequestPayload,
|
|
@@ -35,7 +43,36 @@ import {
|
|
|
35
43
|
} from '~/src/server/schemas/index.js'
|
|
36
44
|
import * as httpService from '~/src/server/services/httpService.js'
|
|
37
45
|
|
|
38
|
-
function
|
|
46
|
+
async function handleHttpEvent(
|
|
47
|
+
request: FormRequest | FormRequestPayload,
|
|
48
|
+
page: PageControllerClass,
|
|
49
|
+
context: FormContext,
|
|
50
|
+
event: Event,
|
|
51
|
+
model: FormModel,
|
|
52
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
53
|
+
) {
|
|
54
|
+
const { options } = event
|
|
55
|
+
const { url } = options
|
|
56
|
+
|
|
57
|
+
// TODO: Update structured data POST payload with when helper
|
|
58
|
+
// is updated to removing the dependency on `SummaryViewModel` etc.
|
|
59
|
+
const viewModel = new SummaryViewModel(request, page, context)
|
|
60
|
+
const items = getFormSubmissionData(viewModel.context, viewModel.details)
|
|
61
|
+
|
|
62
|
+
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
63
|
+
const payload = format(context, items, model, undefined, undefined)
|
|
64
|
+
const opts = { payload }
|
|
65
|
+
|
|
66
|
+
if (preparePageEventRequestOptions) {
|
|
67
|
+
preparePageEventRequestOptions(opts, event, page, context)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { payload: response } = await httpService.postJson(url, opts)
|
|
71
|
+
|
|
72
|
+
Object.assign(context.data, response)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function makeGetHandler(
|
|
39
76
|
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
40
77
|
) {
|
|
41
78
|
return function getHandler(
|
|
@@ -59,28 +96,14 @@ function makeGetHandler(
|
|
|
59
96
|
}
|
|
60
97
|
|
|
61
98
|
if (events?.onLoad && events.onLoad.type === 'http') {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
viewModel.context,
|
|
70
|
-
viewModel.details
|
|
99
|
+
await handleHttpEvent(
|
|
100
|
+
request,
|
|
101
|
+
page,
|
|
102
|
+
context,
|
|
103
|
+
events.onLoad,
|
|
104
|
+
model,
|
|
105
|
+
preparePageEventRequestOptions
|
|
71
106
|
)
|
|
72
|
-
|
|
73
|
-
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
74
|
-
const payload = format(context, items, model, undefined, undefined)
|
|
75
|
-
const opts = { payload }
|
|
76
|
-
|
|
77
|
-
if (preparePageEventRequestOptions) {
|
|
78
|
-
preparePageEventRequestOptions(opts, events.onLoad, page, context)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { payload: response } = await httpService.postJson(url, opts)
|
|
82
|
-
|
|
83
|
-
Object.assign(context.data, response)
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
return page.makeGetRouteHandler()(request, context, h)
|
|
@@ -88,23 +111,56 @@ function makeGetHandler(
|
|
|
88
111
|
}
|
|
89
112
|
}
|
|
90
113
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
114
|
+
export function makePostHandler(
|
|
115
|
+
preparePageEventRequestOptions?: PreparePageEventRequestOptions
|
|
94
116
|
) {
|
|
95
|
-
|
|
117
|
+
return function postHandler(
|
|
118
|
+
request: FormRequestPayload,
|
|
119
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
120
|
+
) {
|
|
121
|
+
const { query } = request
|
|
96
122
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
123
|
+
return redirectOrMakeHandler(request, h, async (page, context) => {
|
|
124
|
+
const { pageDef } = page
|
|
125
|
+
const { isForceAccess } = context
|
|
126
|
+
const { model } = request.app
|
|
127
|
+
const { events } = page
|
|
100
128
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
129
|
+
// Redirect to GET for preview URL direct access
|
|
130
|
+
if (isForceAccess && !hasFormComponents(pageDef)) {
|
|
131
|
+
return proceed(request, h, redirectPath(page.href, query))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!model) {
|
|
135
|
+
throw Boom.notFound(`No model found for /${request.params.path}`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const response = await page.makePostRouteHandler()(request, context, h)
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
events?.onSave &&
|
|
142
|
+
events.onSave.type === 'http' &&
|
|
143
|
+
isSuccessful(response)
|
|
144
|
+
) {
|
|
145
|
+
await handleHttpEvent(
|
|
146
|
+
request,
|
|
147
|
+
page,
|
|
148
|
+
context,
|
|
149
|
+
events.onSave,
|
|
150
|
+
model,
|
|
151
|
+
preparePageEventRequestOptions
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return response
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isSuccessful(response: ResponseObject): boolean {
|
|
161
|
+
const { statusCode } = response
|
|
105
162
|
|
|
106
|
-
|
|
107
|
-
})
|
|
163
|
+
return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400
|
|
108
164
|
}
|
|
109
165
|
|
|
110
166
|
export function getRoutes(
|
|
@@ -174,7 +230,7 @@ export function getRoutes(
|
|
|
174
230
|
{
|
|
175
231
|
method: 'post',
|
|
176
232
|
path: '/{slug}/{path}/{itemId?}',
|
|
177
|
-
handler:
|
|
233
|
+
handler: makePostHandler(preparePageEventRequestOptions),
|
|
178
234
|
options: {
|
|
179
235
|
...postRouteOptions,
|
|
180
236
|
validate: {
|
|
@@ -196,7 +252,7 @@ export function getRoutes(
|
|
|
196
252
|
{
|
|
197
253
|
method: 'post',
|
|
198
254
|
path: '/preview/{state}/{slug}/{path}/{itemId?}',
|
|
199
|
-
handler:
|
|
255
|
+
handler: makePostHandler(preparePageEventRequestOptions),
|
|
200
256
|
options: {
|
|
201
257
|
...postRouteOptions,
|
|
202
258
|
validate: {
|
|
@@ -29,20 +29,19 @@ export const formsService = async () => {
|
|
|
29
29
|
// Instantiate the file loader form service
|
|
30
30
|
const loader = new FileFormService()
|
|
31
31
|
|
|
32
|
-
// Add a
|
|
33
|
-
await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.
|
|
32
|
+
// Add a Yaml form
|
|
33
|
+
await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {
|
|
34
34
|
...metadata,
|
|
35
|
-
id: '
|
|
35
|
+
id: '641aeafd-13dd-40fa-9186-001703800efb',
|
|
36
36
|
title: 'Register as a unicorn breeder',
|
|
37
37
|
slug: 'register-as-a-unicorn-breeder'
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {
|
|
40
|
+
await loader.addForm('src/server/forms/page-events.yaml', {
|
|
42
41
|
...metadata,
|
|
43
|
-
id: '
|
|
44
|
-
title: '
|
|
45
|
-
slug: '
|
|
42
|
+
id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',
|
|
43
|
+
title: 'Page events demo',
|
|
44
|
+
slug: 'page-events-demo'
|
|
46
45
|
})
|
|
47
46
|
|
|
48
47
|
await loader.addForm('src/server/forms/components.json', {
|