@defra/forms-engine-plugin 4.11.2 → 4.12.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/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.server/client/javascripts/location-map.js +1 -1
- package/.server/client/javascripts/location-map.js.map +1 -1
- package/.server/server/plugins/engine/beta/form-context.js +5 -4
- package/.server/server/plugins/engine/beta/form-context.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
- package/.server/server/plugins/engine/form-availability.d.ts +16 -0
- package/.server/server/plugins/engine/form-availability.js +26 -0
- package/.server/server/plugins/engine/form-availability.js.map +1 -0
- package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +8 -0
- package/.server/server/plugins/engine/models/unavailable-view-model.js +21 -0
- package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -0
- package/.server/server/plugins/engine/plugin.js +6 -0
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/unavailable-response.d.ts +9 -0
- package/.server/server/plugins/engine/unavailable-response.js +23 -0
- package/.server/server/plugins/engine/unavailable-response.js.map +1 -0
- package/.server/server/plugins/engine/views/unavailable.html +20 -0
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +4 -4
- package/src/client/javascripts/location-map.js +1 -1
- package/src/server/plugins/engine/beta/form-context.ts +6 -8
- package/src/server/plugins/engine/components/helpers/geospatial.ts +1 -1
- package/src/server/plugins/engine/form-availability.ts +31 -0
- package/src/server/plugins/engine/models/unavailable-view-model.ts +36 -0
- package/src/server/plugins/engine/plugin.ts +6 -0
- package/src/server/plugins/engine/unavailable-response.ts +29 -0
- package/src/server/plugins/engine/views/unavailable.html +20 -0
- package/src/typings/hapi/index.d.ts +1 -1
|
@@ -3,6 +3,7 @@ import { type Request, type Server } from '@hapi/hapi'
|
|
|
3
3
|
import { isEqual } from 'date-fns'
|
|
4
4
|
|
|
5
5
|
import { PREVIEW_PATH_PREFIX } from '../../../constants.js'
|
|
6
|
+
import { assertFormAvailable } from '../form-availability.js'
|
|
6
7
|
import {
|
|
7
8
|
checkEmailAddressForLiveFormSubmission,
|
|
8
9
|
getCacheService
|
|
@@ -52,6 +53,7 @@ export async function getFormModel(
|
|
|
52
53
|
const formState = resolveState(state)
|
|
53
54
|
|
|
54
55
|
const metadata = await formsService.getFormMetadata(slug)
|
|
56
|
+
assertFormAvailable(metadata)
|
|
55
57
|
|
|
56
58
|
const definition = await formsService.getFormDefinition(
|
|
57
59
|
metadata.id,
|
|
@@ -134,6 +136,7 @@ export async function resolveFormModel(
|
|
|
134
136
|
const { formsService } = services
|
|
135
137
|
|
|
136
138
|
const metadata = await formsService.getFormMetadata(slug)
|
|
139
|
+
assertFormAvailable(metadata)
|
|
137
140
|
const formState = resolveState(state)
|
|
138
141
|
const isPreview = options.isPreview ?? isPreviewState(state, options)
|
|
139
142
|
const stateMetadata = metadata[formState]
|
|
@@ -145,15 +148,10 @@ export async function resolveFormModel(
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
// The models cache is created lazily per server instance
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
149
|
-
if (!server.app.models) {
|
|
150
|
-
server.app.models = new Map<string, { model: FormModel; updatedAt: Date }>()
|
|
151
|
-
}
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
>
|
|
152
|
+
server.app.models ??= new Map<string, { model: FormModel; updatedAt: Date }>()
|
|
153
|
+
|
|
154
|
+
const cache = server.app.models
|
|
157
155
|
|
|
158
156
|
const cacheKey = `${metadata.id}_${formState}_${isPreview}`
|
|
159
157
|
let entry = cache.get(cacheKey)
|
|
@@ -116,7 +116,7 @@ export function getGeospatialSchema(country?: GeospatialFieldOptionsCountry) {
|
|
|
116
116
|
return value
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
const result = booleanWithin(value, countryFeature)
|
|
119
|
+
const result = booleanWithin(value as Geometry | Feature, countryFeature)
|
|
120
120
|
|
|
121
121
|
if (!result) {
|
|
122
122
|
return helpers.error('any.custom', {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type FormMetadata } from '@defra/forms-model'
|
|
2
|
+
import Boom from '@hapi/boom'
|
|
3
|
+
|
|
4
|
+
export interface OfflineBoomData {
|
|
5
|
+
offline: true
|
|
6
|
+
metadata: FormMetadata
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Throws when the form has been taken offline. The plugin's
|
|
11
|
+
* unavailable-response extension catches the marker and renders the
|
|
12
|
+
* "Sorry, this form is unavailable" view at HTTP 200.
|
|
13
|
+
*/
|
|
14
|
+
export function assertFormAvailable(metadata: FormMetadata): void {
|
|
15
|
+
if (metadata.offline === true) {
|
|
16
|
+
const data: OfflineBoomData = { offline: true, metadata }
|
|
17
|
+
throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
|
|
18
|
+
statusCode: 503,
|
|
19
|
+
data
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Type guard for the offline Boom marker. */
|
|
25
|
+
export function isOfflineBoom(
|
|
26
|
+
err: unknown
|
|
27
|
+
): err is Boom.Boom<OfflineBoomData> & { data: OfflineBoomData } {
|
|
28
|
+
if (!Boom.isBoom(err)) return false
|
|
29
|
+
const data = err.data as Partial<OfflineBoomData> | null | undefined
|
|
30
|
+
return data?.offline === true && !!data.metadata
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type FormMetadata } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
export interface UnavailableViewModel {
|
|
4
|
+
pageTitle: string
|
|
5
|
+
formTitle: string
|
|
6
|
+
organisationName: string
|
|
7
|
+
phoneLines?: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Defra organisations carry an abbreviation suffix on the enum value, e.g.
|
|
12
|
+
* "Rural Payments Agency – RPA". The unavailable page reads cleanly without it.
|
|
13
|
+
*/
|
|
14
|
+
function stripOrgSuffix(organisation: string) {
|
|
15
|
+
return organisation.split(' – ')[0]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function splitPhoneLines(phone: string | undefined) {
|
|
19
|
+
if (!phone) return undefined
|
|
20
|
+
const lines = phone
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map((line) => line.trim())
|
|
23
|
+
.filter((line) => line.length > 0)
|
|
24
|
+
return lines.length > 0 ? lines : undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function unavailableViewModel(
|
|
28
|
+
metadata: FormMetadata
|
|
29
|
+
): UnavailableViewModel {
|
|
30
|
+
return {
|
|
31
|
+
pageTitle: 'Sorry, this form is unavailable',
|
|
32
|
+
formTitle: metadata.title,
|
|
33
|
+
organisationName: stripOrgSuffix(metadata.organisation),
|
|
34
|
+
phoneLines: splitPhoneLines(metadata.contact?.phone)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -15,6 +15,7 @@ import { getRoutes as getQuestionRoutes } from './routes/questions.js'
|
|
|
15
15
|
import { getRoutes as getRepeaterItemDeleteRoutes } from './routes/repeaters/item-delete.js'
|
|
16
16
|
import { getRoutes as getRepeaterSummaryRoutes } from './routes/repeaters/summary.js'
|
|
17
17
|
import { type PluginOptions } from './types.js'
|
|
18
|
+
import { registerUnavailableResponse } from './unavailable-response.js'
|
|
18
19
|
import { registerVision } from './vision.js'
|
|
19
20
|
import { mapPlugin } from '../map/index.js'
|
|
20
21
|
import { postcodeLookupPlugin } from '../postcode-lookup/index.js'
|
|
@@ -129,5 +130,10 @@ export const plugin = {
|
|
|
129
130
|
]
|
|
130
131
|
|
|
131
132
|
server.route(routes as unknown as ServerRoute[]) // TODO
|
|
133
|
+
|
|
134
|
+
// Registration order is important: must be registered after the engine's
|
|
135
|
+
// routes so it sees their responses, but before any global error-page
|
|
136
|
+
// handler that would re-shape Boom errors.
|
|
137
|
+
registerUnavailableResponse(server)
|
|
132
138
|
}
|
|
133
139
|
} satisfies Plugin<PluginOptions>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Request, type ResponseToolkit, type Server } from '@hapi/hapi'
|
|
2
|
+
|
|
3
|
+
import { isOfflineBoom } from './form-availability.js'
|
|
4
|
+
import { unavailableViewModel } from './models/unavailable-view-model.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Registers a server-wide onPreResponse extension that intercepts the offline
|
|
8
|
+
* Boom thrown and renders the unavailable view.
|
|
9
|
+
*
|
|
10
|
+
* Must be registered after the engine's routes so it sees their responses,
|
|
11
|
+
* but before any global error-page handler that would re-shape Boom errors.
|
|
12
|
+
*/
|
|
13
|
+
export function registerUnavailableResponse(server: Server) {
|
|
14
|
+
server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {
|
|
15
|
+
const response = request.response
|
|
16
|
+
if (!isOfflineBoom(response)) {
|
|
17
|
+
return h.continue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { metadata } = response.data
|
|
21
|
+
|
|
22
|
+
return h
|
|
23
|
+
.view('unavailable', unavailableViewModel(metadata))
|
|
24
|
+
.header('Cache-Control', 'no-store, no-cache, must-revalidate')
|
|
25
|
+
.header('X-Robots-Tag', 'noindex, nofollow')
|
|
26
|
+
.code(200)
|
|
27
|
+
.takeover()
|
|
28
|
+
})
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{% extends baseLayoutPath %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<div class="govuk-grid-row">
|
|
5
|
+
<div class="govuk-grid-column-two-thirds">
|
|
6
|
+
<h1 class="govuk-heading-l">Sorry, this form is unavailable</h1>
|
|
7
|
+
<p class="govuk-body">'{{ formTitle }}' has been archived and is no longer available.</p>
|
|
8
|
+
<p class="govuk-body">Contact the {{ organisationName }}.</p>
|
|
9
|
+
|
|
10
|
+
{% if phoneLines %}
|
|
11
|
+
<ul class="govuk-list govuk-list--bullet">
|
|
12
|
+
{% for line in phoneLines %}
|
|
13
|
+
<li>{{ line }}</li>
|
|
14
|
+
{% endfor %}
|
|
15
|
+
</ul>
|
|
16
|
+
<p class="govuk-body"><a href="https://www.gov.uk/call-charges" class="govuk-link govuk-link--no-visited-state">Find out about call charges</a></p>
|
|
17
|
+
{% endif %}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
{% endblock %}
|