@defra/forms-engine-plugin 1.0.0 → 1.0.2
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/README.md +56 -0
- package/.server/server/plugins/engine/configureEnginePlugin.d.ts +2 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/plugin.d.ts +2 -27
- package/.server/server/plugins/engine/plugin.js +17 -594
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/registrationOptions.d.ts +1 -0
- package/.server/server/plugins/engine/registrationOptions.js +2 -0
- package/.server/server/plugins/engine/registrationOptions.js.map +1 -0
- package/.server/server/plugins/engine/routes/file-upload.d.ts +4 -0
- package/.server/server/plugins/engine/routes/file-upload.js +41 -0
- package/.server/server/plugins/engine/routes/file-upload.js.map +1 -0
- package/.server/server/plugins/engine/routes/index.d.ts +7 -0
- package/.server/server/plugins/engine/routes/index.js +141 -0
- package/.server/server/plugins/engine/routes/index.js.map +1 -0
- package/.server/server/plugins/engine/routes/questions.d.ts +3 -0
- package/.server/server/plugins/engine/routes/questions.js +168 -0
- package/.server/server/plugins/engine/routes/questions.js.map +1 -0
- package/.server/server/plugins/engine/routes/repeaters/item-delete.d.ts +3 -0
- package/.server/server/plugins/engine/routes/repeaters/item-delete.js +106 -0
- package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -0
- package/.server/server/plugins/engine/routes/repeaters/summary.d.ts +3 -0
- package/.server/server/plugins/engine/routes/repeaters/summary.js +98 -0
- package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -0
- package/.server/server/plugins/engine/types.d.ts +19 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/vision.d.ts +12 -0
- package/.server/server/plugins/engine/vision.js +55 -0
- package/.server/server/plugins/engine/vision.js.map +1 -0
- package/.server/server/services/cacheService.d.ts +12 -3
- package/.server/server/services/cacheService.js +35 -8
- package/.server/server/services/cacheService.js.map +1 -1
- package/package.json +3 -3
- package/src/server/plugins/engine/README.md +56 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +3 -5
- package/src/server/plugins/engine/plugin.ts +35 -765
- package/src/server/plugins/engine/registrationOptions.ts +0 -0
- package/src/server/plugins/engine/routes/file-upload.ts +54 -0
- package/src/server/plugins/engine/routes/index.ts +187 -0
- package/src/server/plugins/engine/routes/questions.ts +208 -0
- package/src/server/plugins/engine/routes/repeaters/item-delete.ts +157 -0
- package/src/server/plugins/engine/routes/repeaters/summary.ts +137 -0
- package/src/server/plugins/engine/types.ts +26 -1
- package/src/server/plugins/engine/vision.ts +95 -0
- package/src/server/services/cacheService.test.ts +98 -2
- package/src/server/services/cacheService.ts +57 -8
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getErrorMessage } from '@defra/forms-model'
|
|
2
|
+
import { type ResponseToolkit, type ServerRoute } from '@hapi/hapi'
|
|
3
|
+
import Joi from 'joi'
|
|
4
|
+
|
|
5
|
+
import { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'
|
|
6
|
+
import {
|
|
7
|
+
type FormRequest,
|
|
8
|
+
type FormRequestRefs
|
|
9
|
+
} from '~/src/server/routes/types.js'
|
|
10
|
+
|
|
11
|
+
export async function getHandler(
|
|
12
|
+
request: FormRequest,
|
|
13
|
+
h: Pick<ResponseToolkit, 'response'>
|
|
14
|
+
) {
|
|
15
|
+
const { uploadId } = request.params as unknown as {
|
|
16
|
+
uploadId: string
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const status = await getUploadStatus(uploadId)
|
|
20
|
+
|
|
21
|
+
if (!status) {
|
|
22
|
+
return h.response({ error: 'Status check failed' }).code(400)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return h.response(status)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const errMsg = getErrorMessage(error)
|
|
28
|
+
request.logger.error(
|
|
29
|
+
errMsg,
|
|
30
|
+
`[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${errMsg}`
|
|
31
|
+
)
|
|
32
|
+
return h.response({ error: 'Status check error' }).code(500)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getRoutes(): ServerRoute<FormRequestRefs>[] {
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
method: 'get',
|
|
40
|
+
path: '/upload-status/{uploadId}',
|
|
41
|
+
handler: getHandler,
|
|
42
|
+
options: {
|
|
43
|
+
plugins: {
|
|
44
|
+
crumb: false
|
|
45
|
+
},
|
|
46
|
+
validate: {
|
|
47
|
+
params: Joi.object().keys({
|
|
48
|
+
uploadId: Joi.string().guid().required()
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import Boom from '@hapi/boom'
|
|
2
|
+
import {
|
|
3
|
+
type ResponseObject,
|
|
4
|
+
type ResponseToolkit,
|
|
5
|
+
type Server
|
|
6
|
+
} from '@hapi/hapi'
|
|
7
|
+
import { isEqual } from 'date-fns'
|
|
8
|
+
|
|
9
|
+
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
|
|
10
|
+
import {
|
|
11
|
+
checkEmailAddressForLiveFormSubmission,
|
|
12
|
+
checkFormStatus,
|
|
13
|
+
findPage,
|
|
14
|
+
getCacheService,
|
|
15
|
+
getPage,
|
|
16
|
+
getStartPath,
|
|
17
|
+
proceed
|
|
18
|
+
} from '~/src/server/plugins/engine/helpers.js'
|
|
19
|
+
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
20
|
+
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
|
|
21
|
+
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
|
|
22
|
+
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
23
|
+
import {
|
|
24
|
+
type FormContext,
|
|
25
|
+
type PluginOptions
|
|
26
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
27
|
+
import {
|
|
28
|
+
type FormRequest,
|
|
29
|
+
type FormRequestPayload
|
|
30
|
+
} from '~/src/server/routes/types.js'
|
|
31
|
+
|
|
32
|
+
export async function redirectOrMakeHandler(
|
|
33
|
+
request: FormRequest | FormRequestPayload,
|
|
34
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
35
|
+
makeHandler: (
|
|
36
|
+
page: PageControllerClass,
|
|
37
|
+
context: FormContext
|
|
38
|
+
) => ResponseObject | Promise<ResponseObject>
|
|
39
|
+
) {
|
|
40
|
+
const { app, params } = request
|
|
41
|
+
const { model } = app
|
|
42
|
+
|
|
43
|
+
if (!model) {
|
|
44
|
+
throw Boom.notFound(`No model found for /${params.path}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const cacheService = getCacheService(request.server)
|
|
48
|
+
const page = getPage(model, request)
|
|
49
|
+
let state = await page.getState(request)
|
|
50
|
+
|
|
51
|
+
if (!state.$$__referenceNumber) {
|
|
52
|
+
const prefix = model.def.metadata?.referenceNumberPrefix ?? ''
|
|
53
|
+
|
|
54
|
+
if (typeof prefix !== 'string') {
|
|
55
|
+
throw Boom.badImplementation(
|
|
56
|
+
'Reference number prefix must be a string or undefined'
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const referenceNumber = generateUniqueReference(prefix)
|
|
61
|
+
state = await page.mergeState(request, state, {
|
|
62
|
+
$$__referenceNumber: referenceNumber
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const flash = cacheService.getFlash(request)
|
|
67
|
+
const context = model.getFormContext(request, state, flash?.errors)
|
|
68
|
+
const relevantPath = page.getRelevantPath(request, context)
|
|
69
|
+
const summaryPath = page.getSummaryPath()
|
|
70
|
+
|
|
71
|
+
// Return handler for relevant pages or preview URL direct access
|
|
72
|
+
if (relevantPath.startsWith(page.path) || context.isForceAccess) {
|
|
73
|
+
return makeHandler(page, context)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Redirect back to last relevant page
|
|
77
|
+
const redirectTo = findPage(model, relevantPath)
|
|
78
|
+
|
|
79
|
+
// Set the return URL unless an exit page
|
|
80
|
+
if (redirectTo?.next.length) {
|
|
81
|
+
request.query.returnUrl = page.getHref(summaryPath)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return proceed(request, h, page.getHref(relevantPath))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong
|
|
89
|
+
const prefix = server.realm.modifiers.route.prefix ?? ''
|
|
90
|
+
|
|
91
|
+
const { services = defaultServices, controllers } = options
|
|
92
|
+
|
|
93
|
+
const { formsService } = services
|
|
94
|
+
|
|
95
|
+
async function handler(
|
|
96
|
+
request: FormRequest | FormRequestPayload,
|
|
97
|
+
h: ResponseToolkit
|
|
98
|
+
) {
|
|
99
|
+
if (server.app.model) {
|
|
100
|
+
request.app.model = server.app.model
|
|
101
|
+
|
|
102
|
+
return h.continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { params } = request
|
|
106
|
+
const { slug } = params
|
|
107
|
+
const { isPreview, state: formState } = checkFormStatus(params)
|
|
108
|
+
|
|
109
|
+
// Get the form metadata using the `slug` param
|
|
110
|
+
const metadata = await formsService.getFormMetadata(slug)
|
|
111
|
+
|
|
112
|
+
const { id, [formState]: state } = metadata
|
|
113
|
+
|
|
114
|
+
// Check the metadata supports the requested state
|
|
115
|
+
if (!state) {
|
|
116
|
+
throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Cache the models based on id, state and whether
|
|
120
|
+
// it's a preview or not. There could be up to 3 models
|
|
121
|
+
// cached for a single form:
|
|
122
|
+
// "{id}_live_false" (live/live)
|
|
123
|
+
// "{id}_live_true" (live/preview)
|
|
124
|
+
// "{id}_draft_true" (draft/preview)
|
|
125
|
+
const key = `${id}_${formState}_${isPreview}`
|
|
126
|
+
let item = server.app.models.get(key)
|
|
127
|
+
|
|
128
|
+
if (!item || !isEqual(item.updatedAt, state.updatedAt)) {
|
|
129
|
+
server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)
|
|
130
|
+
|
|
131
|
+
// Get the form definition using the `id` from the metadata
|
|
132
|
+
const definition = await formsService.getFormDefinition(id, formState)
|
|
133
|
+
|
|
134
|
+
if (!definition) {
|
|
135
|
+
throw Boom.notFound(
|
|
136
|
+
`No definition found for form metadata ${id} (${slug}) ${formState}`
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const emailAddress = metadata.notificationEmail ?? definition.outputEmail
|
|
141
|
+
|
|
142
|
+
checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)
|
|
143
|
+
|
|
144
|
+
// Build the form model
|
|
145
|
+
server.logger.info(
|
|
146
|
+
`Building model for form definition ${id} (${slug}) ${formState}`
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
// Set up the basePath for the model
|
|
150
|
+
const basePath = (
|
|
151
|
+
isPreview
|
|
152
|
+
? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`
|
|
153
|
+
: `${prefix}/${slug}`
|
|
154
|
+
).substring(1)
|
|
155
|
+
|
|
156
|
+
// Construct the form model
|
|
157
|
+
const model = new FormModel(
|
|
158
|
+
definition,
|
|
159
|
+
{ basePath },
|
|
160
|
+
services,
|
|
161
|
+
controllers
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// Create new item and add it to the item cache
|
|
165
|
+
item = { model, updatedAt: state.updatedAt }
|
|
166
|
+
server.app.models.set(key, item)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Assign the model to the request data
|
|
170
|
+
// for use in the downstream handler
|
|
171
|
+
request.app.model = item.model
|
|
172
|
+
|
|
173
|
+
return h.continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return handler
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function dispatchHandler(
|
|
180
|
+
request: FormRequest,
|
|
181
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
182
|
+
) {
|
|
183
|
+
const { model } = request.app
|
|
184
|
+
|
|
185
|
+
const servicePath = model ? `/${model.basePath}` : ''
|
|
186
|
+
return proceed(request, h, `${servicePath}${getStartPath(model)}`)
|
|
187
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { hasFormComponents, slugSchema } from '@defra/forms-model'
|
|
2
|
+
import Boom from '@hapi/boom'
|
|
3
|
+
import {
|
|
4
|
+
type ResponseToolkit,
|
|
5
|
+
type RouteOptions,
|
|
6
|
+
type ServerRoute
|
|
7
|
+
} from '@hapi/hapi'
|
|
8
|
+
import Joi from 'joi'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
normalisePath,
|
|
12
|
+
proceed,
|
|
13
|
+
redirectPath
|
|
14
|
+
} from '~/src/server/plugins/engine/helpers.js'
|
|
15
|
+
import { SummaryViewModel } from '~/src/server/plugins/engine/models/index.js'
|
|
16
|
+
import { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'
|
|
17
|
+
import { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
|
|
18
|
+
import {
|
|
19
|
+
dispatchHandler,
|
|
20
|
+
redirectOrMakeHandler
|
|
21
|
+
} from '~/src/server/plugins/engine/routes/index.js'
|
|
22
|
+
import {
|
|
23
|
+
type FormRequest,
|
|
24
|
+
type FormRequestPayload,
|
|
25
|
+
type FormRequestPayloadRefs,
|
|
26
|
+
type FormRequestRefs
|
|
27
|
+
} from '~/src/server/routes/types.js'
|
|
28
|
+
import {
|
|
29
|
+
actionSchema,
|
|
30
|
+
crumbSchema,
|
|
31
|
+
itemIdSchema,
|
|
32
|
+
pathSchema,
|
|
33
|
+
stateSchema
|
|
34
|
+
} from '~/src/server/schemas/index.js'
|
|
35
|
+
import * as httpService from '~/src/server/services/httpService.js'
|
|
36
|
+
|
|
37
|
+
function getHandler(
|
|
38
|
+
request: FormRequest,
|
|
39
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
40
|
+
) {
|
|
41
|
+
const { params } = request
|
|
42
|
+
|
|
43
|
+
if (normalisePath(params.path) === '') {
|
|
44
|
+
return dispatchHandler(request, h)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return redirectOrMakeHandler(request, h, async (page, context) => {
|
|
48
|
+
// Check for a page onLoad HTTP event and if one exists,
|
|
49
|
+
// call it and assign the response to the context data
|
|
50
|
+
const { events } = page
|
|
51
|
+
const { model } = request.app
|
|
52
|
+
|
|
53
|
+
if (!model) {
|
|
54
|
+
throw Boom.notFound(`No model found for /${params.path}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (events?.onLoad && events.onLoad.type === 'http') {
|
|
58
|
+
const { options } = events.onLoad
|
|
59
|
+
const { url } = options
|
|
60
|
+
|
|
61
|
+
// TODO: Update structured data POST payload with when helper
|
|
62
|
+
// is updated to removing the dependency on `SummaryViewModel` etc.
|
|
63
|
+
const viewModel = new SummaryViewModel(request, page, context)
|
|
64
|
+
const items = getFormSubmissionData(viewModel.context, viewModel.details)
|
|
65
|
+
|
|
66
|
+
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
67
|
+
const payload = format(items, model, undefined, undefined)
|
|
68
|
+
|
|
69
|
+
const { payload: response } = await httpService.postJson(url, {
|
|
70
|
+
payload
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
Object.assign(context.data, response)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return page.makeGetRouteHandler()(request, context, h)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function postHandler(
|
|
81
|
+
request: FormRequestPayload,
|
|
82
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
83
|
+
) {
|
|
84
|
+
const { query } = request
|
|
85
|
+
|
|
86
|
+
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
87
|
+
const { pageDef } = page
|
|
88
|
+
const { isForceAccess } = context
|
|
89
|
+
|
|
90
|
+
// Redirect to GET for preview URL direct access
|
|
91
|
+
if (isForceAccess && !hasFormComponents(pageDef)) {
|
|
92
|
+
return proceed(request, h, redirectPath(page.href, query))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return page.makePostRouteHandler()(request, context, h)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getRoutes(
|
|
100
|
+
getRouteOptions: RouteOptions<FormRequestRefs>,
|
|
101
|
+
postRouteOptions: RouteOptions<FormRequestPayloadRefs>
|
|
102
|
+
): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
method: 'get',
|
|
106
|
+
path: '/{slug}',
|
|
107
|
+
handler: getHandler,
|
|
108
|
+
options: {
|
|
109
|
+
...getRouteOptions,
|
|
110
|
+
validate: {
|
|
111
|
+
params: Joi.object().keys({
|
|
112
|
+
slug: slugSchema
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
method: 'get',
|
|
119
|
+
path: '/preview/{state}/{slug}',
|
|
120
|
+
handler: dispatchHandler,
|
|
121
|
+
options: {
|
|
122
|
+
...getRouteOptions,
|
|
123
|
+
validate: {
|
|
124
|
+
params: Joi.object().keys({
|
|
125
|
+
state: stateSchema,
|
|
126
|
+
slug: slugSchema
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
method: 'get',
|
|
133
|
+
path: '/{slug}/{path}/{itemId?}',
|
|
134
|
+
handler: getHandler,
|
|
135
|
+
options: {
|
|
136
|
+
...getRouteOptions,
|
|
137
|
+
validate: {
|
|
138
|
+
params: Joi.object().keys({
|
|
139
|
+
slug: slugSchema,
|
|
140
|
+
path: pathSchema,
|
|
141
|
+
itemId: itemIdSchema.optional()
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
method: 'get',
|
|
148
|
+
path: '/preview/{state}/{slug}/{path}/{itemId?}',
|
|
149
|
+
handler: getHandler,
|
|
150
|
+
options: {
|
|
151
|
+
...getRouteOptions,
|
|
152
|
+
validate: {
|
|
153
|
+
params: Joi.object().keys({
|
|
154
|
+
state: stateSchema,
|
|
155
|
+
slug: slugSchema,
|
|
156
|
+
path: pathSchema,
|
|
157
|
+
itemId: itemIdSchema.optional()
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
method: 'post',
|
|
164
|
+
path: '/{slug}/{path}/{itemId?}',
|
|
165
|
+
handler: postHandler,
|
|
166
|
+
options: {
|
|
167
|
+
...postRouteOptions,
|
|
168
|
+
validate: {
|
|
169
|
+
params: Joi.object().keys({
|
|
170
|
+
slug: slugSchema,
|
|
171
|
+
path: pathSchema,
|
|
172
|
+
itemId: itemIdSchema.optional()
|
|
173
|
+
}),
|
|
174
|
+
payload: Joi.object()
|
|
175
|
+
.keys({
|
|
176
|
+
crumb: crumbSchema,
|
|
177
|
+
action: actionSchema
|
|
178
|
+
})
|
|
179
|
+
.unknown(true)
|
|
180
|
+
.required()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
method: 'post',
|
|
186
|
+
path: '/preview/{state}/{slug}/{path}/{itemId?}',
|
|
187
|
+
handler: postHandler,
|
|
188
|
+
options: {
|
|
189
|
+
...postRouteOptions,
|
|
190
|
+
validate: {
|
|
191
|
+
params: Joi.object().keys({
|
|
192
|
+
state: stateSchema,
|
|
193
|
+
slug: slugSchema,
|
|
194
|
+
path: pathSchema,
|
|
195
|
+
itemId: itemIdSchema.optional()
|
|
196
|
+
}),
|
|
197
|
+
payload: Joi.object()
|
|
198
|
+
.keys({
|
|
199
|
+
crumb: crumbSchema,
|
|
200
|
+
action: actionSchema
|
|
201
|
+
})
|
|
202
|
+
.unknown(true)
|
|
203
|
+
.required()
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { slugSchema } from '@defra/forms-model'
|
|
2
|
+
import Boom from '@hapi/boom'
|
|
3
|
+
import {
|
|
4
|
+
type ResponseToolkit,
|
|
5
|
+
type RouteOptions,
|
|
6
|
+
type ServerRoute
|
|
7
|
+
} from '@hapi/hapi'
|
|
8
|
+
import Joi from 'joi'
|
|
9
|
+
|
|
10
|
+
import { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
|
|
11
|
+
import { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
|
|
12
|
+
import { redirectOrMakeHandler } from '~/src/server/plugins/engine/routes/index.js'
|
|
13
|
+
import {
|
|
14
|
+
type FormRequest,
|
|
15
|
+
type FormRequestPayload,
|
|
16
|
+
type FormRequestPayloadRefs,
|
|
17
|
+
type FormRequestRefs
|
|
18
|
+
} from '~/src/server/routes/types.js'
|
|
19
|
+
import {
|
|
20
|
+
actionSchema,
|
|
21
|
+
confirmSchema,
|
|
22
|
+
crumbSchema,
|
|
23
|
+
itemIdSchema,
|
|
24
|
+
pathSchema,
|
|
25
|
+
stateSchema
|
|
26
|
+
} from '~/src/server/schemas/index.js'
|
|
27
|
+
|
|
28
|
+
// Item delete GET route
|
|
29
|
+
function getHandler(
|
|
30
|
+
request: FormRequest,
|
|
31
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
32
|
+
) {
|
|
33
|
+
const { params } = request
|
|
34
|
+
|
|
35
|
+
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
36
|
+
if (
|
|
37
|
+
!(
|
|
38
|
+
page instanceof RepeatPageController ||
|
|
39
|
+
page instanceof FileUploadPageController
|
|
40
|
+
)
|
|
41
|
+
) {
|
|
42
|
+
throw Boom.notFound(`No page found for /${params.path}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return page.makeGetItemDeleteRouteHandler()(request, context, h)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function postHandler(
|
|
50
|
+
request: FormRequestPayload,
|
|
51
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
52
|
+
) {
|
|
53
|
+
const { params } = request
|
|
54
|
+
|
|
55
|
+
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
56
|
+
const { isForceAccess } = context
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
isForceAccess ||
|
|
60
|
+
!(
|
|
61
|
+
page instanceof RepeatPageController ||
|
|
62
|
+
page instanceof FileUploadPageController
|
|
63
|
+
)
|
|
64
|
+
) {
|
|
65
|
+
throw Boom.notFound(`No page found for /${params.path}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return page.makePostItemDeleteRouteHandler()(request, context, h)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getRoutes(
|
|
73
|
+
getRouteOptions: RouteOptions<FormRequestRefs>,
|
|
74
|
+
postRouteOptions: RouteOptions<FormRequestPayloadRefs>
|
|
75
|
+
): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
method: 'get',
|
|
79
|
+
path: '/{slug}/{path}/{itemId}/confirm-delete',
|
|
80
|
+
handler: getHandler,
|
|
81
|
+
options: {
|
|
82
|
+
...getRouteOptions,
|
|
83
|
+
validate: {
|
|
84
|
+
params: Joi.object().keys({
|
|
85
|
+
slug: slugSchema,
|
|
86
|
+
path: pathSchema,
|
|
87
|
+
itemId: itemIdSchema
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
method: 'get',
|
|
95
|
+
path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',
|
|
96
|
+
handler: getHandler,
|
|
97
|
+
options: {
|
|
98
|
+
...getRouteOptions,
|
|
99
|
+
validate: {
|
|
100
|
+
params: Joi.object().keys({
|
|
101
|
+
state: stateSchema,
|
|
102
|
+
slug: slugSchema,
|
|
103
|
+
path: pathSchema,
|
|
104
|
+
itemId: itemIdSchema
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
method: 'post',
|
|
112
|
+
path: '/{slug}/{path}/{itemId}/confirm-delete',
|
|
113
|
+
handler: postHandler,
|
|
114
|
+
options: {
|
|
115
|
+
...postRouteOptions,
|
|
116
|
+
validate: {
|
|
117
|
+
params: Joi.object().keys({
|
|
118
|
+
slug: slugSchema,
|
|
119
|
+
path: pathSchema,
|
|
120
|
+
itemId: itemIdSchema
|
|
121
|
+
}),
|
|
122
|
+
payload: Joi.object()
|
|
123
|
+
.keys({
|
|
124
|
+
crumb: crumbSchema,
|
|
125
|
+
action: actionSchema,
|
|
126
|
+
confirm: confirmSchema
|
|
127
|
+
})
|
|
128
|
+
.required()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
{
|
|
134
|
+
method: 'post',
|
|
135
|
+
path: '/preview/{state}/{slug}/{path}/{itemId}/confirm-delete',
|
|
136
|
+
handler: postHandler,
|
|
137
|
+
options: {
|
|
138
|
+
...postRouteOptions,
|
|
139
|
+
validate: {
|
|
140
|
+
params: Joi.object().keys({
|
|
141
|
+
state: stateSchema,
|
|
142
|
+
slug: slugSchema,
|
|
143
|
+
path: pathSchema,
|
|
144
|
+
itemId: itemIdSchema
|
|
145
|
+
}),
|
|
146
|
+
payload: Joi.object()
|
|
147
|
+
.keys({
|
|
148
|
+
crumb: crumbSchema,
|
|
149
|
+
action: actionSchema,
|
|
150
|
+
confirm: confirmSchema
|
|
151
|
+
})
|
|
152
|
+
.required()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|