@defra/forms-engine-plugin 0.0.4 → 0.0.5
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/package.json +3 -2
- package/src/client/javascripts/application.js +87 -0
- package/src/client/javascripts/file-upload.js +386 -0
- package/src/client/stylesheets/_code.scss +33 -0
- package/src/client/stylesheets/_govuk-frontend.scss +4 -0
- package/src/client/stylesheets/_prose.scss +56 -0
- package/src/client/stylesheets/_service-banner.scss +24 -0
- package/src/client/stylesheets/_summary-list.scss +28 -0
- package/src/client/stylesheets/_tag-env.scss +24 -0
- package/src/client/stylesheets/application.scss +14 -0
- package/src/common/cookies.js +58 -0
- package/src/common/cookies.test.js +23 -0
- package/src/common/types.js +5 -0
- package/src/config/index.ts +271 -0
- package/src/index.ts +31 -0
- package/src/server/common/helpers/logging/logger-options.test.ts +50 -0
- package/src/server/common/helpers/logging/logger-options.ts +46 -0
- package/src/server/common/helpers/logging/logger.ts +7 -0
- package/src/server/common/helpers/logging/request-logger.ts +9 -0
- package/src/server/common/helpers/logging/request-tracing.js +10 -0
- package/src/server/common/helpers/redis-client.js +70 -0
- package/src/server/constants.js +1 -0
- package/src/server/forms/README.md +10 -0
- package/src/server/forms/components.json +1015 -0
- package/src/server/forms/report-a-terrorist.json +270 -0
- package/src/server/forms/runner-components-test.json +365 -0
- package/src/server/forms/test.json +581 -0
- package/src/server/index.test.ts +582 -0
- package/src/server/index.ts +140 -0
- package/src/server/plugins/blankie.test.ts +73 -0
- package/src/server/plugins/blankie.ts +48 -0
- package/src/server/plugins/crumb.ts +20 -0
- package/src/server/plugins/engine/README.md +87 -0
- package/src/server/plugins/engine/components/AutocompleteField.test.ts +294 -0
- package/src/server/plugins/engine/components/AutocompleteField.ts +49 -0
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +379 -0
- package/src/server/plugins/engine/components/CheckboxesField.ts +106 -0
- package/src/server/plugins/engine/components/ComponentBase.ts +97 -0
- package/src/server/plugins/engine/components/ComponentCollection.ts +278 -0
- package/src/server/plugins/engine/components/DatePartsField.test.ts +822 -0
- package/src/server/plugins/engine/components/DatePartsField.ts +264 -0
- package/src/server/plugins/engine/components/Details.test.ts +49 -0
- package/src/server/plugins/engine/components/Details.ts +30 -0
- package/src/server/plugins/engine/components/EmailAddressField.test.ts +395 -0
- package/src/server/plugins/engine/components/EmailAddressField.ts +55 -0
- package/src/server/plugins/engine/components/FileUploadField.test.ts +778 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +262 -0
- package/src/server/plugins/engine/components/FormComponent.ts +249 -0
- package/src/server/plugins/engine/components/Html.test.ts +48 -0
- package/src/server/plugins/engine/components/Html.ts +29 -0
- package/src/server/plugins/engine/components/InsetText.test.ts +48 -0
- package/src/server/plugins/engine/components/InsetText.ts +27 -0
- package/src/server/plugins/engine/components/List.test.ts +76 -0
- package/src/server/plugins/engine/components/List.ts +72 -0
- package/src/server/plugins/engine/components/ListFormComponent.ts +140 -0
- package/src/server/plugins/engine/components/MonthYearField.test.ts +567 -0
- package/src/server/plugins/engine/components/MonthYearField.ts +222 -0
- package/src/server/plugins/engine/components/MultilineTextField.test.ts +558 -0
- package/src/server/plugins/engine/components/MultilineTextField.ts +138 -0
- package/src/server/plugins/engine/components/NumberField.test.ts +701 -0
- package/src/server/plugins/engine/components/NumberField.ts +163 -0
- package/src/server/plugins/engine/components/RadiosField.test.ts +288 -0
- package/src/server/plugins/engine/components/RadiosField.ts +24 -0
- package/src/server/plugins/engine/components/SelectField.test.ts +288 -0
- package/src/server/plugins/engine/components/SelectField.ts +47 -0
- package/src/server/plugins/engine/components/SelectionControlField.ts +43 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +356 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.ts +67 -0
- package/src/server/plugins/engine/components/TextField.test.ts +489 -0
- package/src/server/plugins/engine/components/TextField.ts +96 -0
- package/src/server/plugins/engine/components/UkAddressField.test.ts +623 -0
- package/src/server/plugins/engine/components/UkAddressField.ts +172 -0
- package/src/server/plugins/engine/components/YesNoField.test.ts +248 -0
- package/src/server/plugins/engine/components/YesNoField.ts +31 -0
- package/src/server/plugins/engine/components/constants.ts +1 -0
- package/src/server/plugins/engine/components/helpers.ts +330 -0
- package/src/server/plugins/engine/components/index.ts +24 -0
- package/src/server/plugins/engine/components/types.ts +117 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +47 -0
- package/src/server/plugins/engine/helpers.test.ts +791 -0
- package/src/server/plugins/engine/helpers.ts +379 -0
- package/src/server/plugins/engine/index.ts +7 -0
- package/src/server/plugins/engine/models/FormModel.test.ts +42 -0
- package/src/server/plugins/engine/models/FormModel.ts +443 -0
- package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
- package/src/server/plugins/engine/models/Section.ts +0 -0
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +209 -0
- package/src/server/plugins/engine/models/SummaryViewModel.ts +220 -0
- package/src/server/plugins/engine/models/index.ts +2 -0
- package/src/server/plugins/engine/models/types.ts +114 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +143 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +73 -0
- package/src/server/plugins/engine/outputFormatters/index.test.ts +17 -0
- package/src/server/plugins/engine/outputFormatters/index.ts +44 -0
- package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +229 -0
- package/src/server/plugins/engine/outputFormatters/machine/v1.ts +140 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +229 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +153 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1108 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +446 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +205 -0
- package/src/server/plugins/engine/pageControllers/PageController.ts +176 -0
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1264 -0
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +561 -0
- package/src/server/plugins/engine/pageControllers/README.md +28 -0
- package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +264 -0
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +458 -0
- package/src/server/plugins/engine/pageControllers/StartPageController.ts +18 -0
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +50 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +261 -0
- package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +28 -0
- package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +19 -0
- package/src/server/plugins/engine/pageControllers/helpers.test.ts +198 -0
- package/src/server/plugins/engine/pageControllers/helpers.ts +101 -0
- package/src/server/plugins/engine/pageControllers/index.ts +10 -0
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +89 -0
- package/src/server/plugins/engine/plugin.ts +673 -0
- package/src/server/plugins/engine/services/formSubmissionService.js +46 -0
- package/src/server/plugins/engine/services/formsService.js +46 -0
- package/src/server/plugins/engine/services/formsService.test.js +90 -0
- package/src/server/plugins/engine/services/index.js +3 -0
- package/src/server/plugins/engine/services/notifyService.test.ts +132 -0
- package/src/server/plugins/engine/services/notifyService.ts +64 -0
- package/src/server/plugins/engine/services/uploadService.js +60 -0
- package/src/server/plugins/engine/types.ts +315 -0
- package/src/server/plugins/engine/views/components/autocompletefield.html +5 -0
- package/src/server/plugins/engine/views/components/checkboxesfield.html +5 -0
- package/src/server/plugins/engine/views/components/datepartsfield.html +5 -0
- package/src/server/plugins/engine/views/components/details.html +6 -0
- package/src/server/plugins/engine/views/components/emailaddressfield.html +5 -0
- package/src/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
- package/src/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
- package/src/server/plugins/engine/views/components/fileuploadfield.html +24 -0
- package/src/server/plugins/engine/views/components/html.html +3 -0
- package/src/server/plugins/engine/views/components/insettext.html +7 -0
- package/src/server/plugins/engine/views/components/list.html +36 -0
- package/src/server/plugins/engine/views/components/monthyearfield.html +5 -0
- package/src/server/plugins/engine/views/components/multilinetextfield.html +10 -0
- package/src/server/plugins/engine/views/components/numberfield.html +5 -0
- package/src/server/plugins/engine/views/components/radiosfield.html +5 -0
- package/src/server/plugins/engine/views/components/selectfield.html +5 -0
- package/src/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
- package/src/server/plugins/engine/views/components/textfield.html +5 -0
- package/src/server/plugins/engine/views/components/ukaddressfield.html +25 -0
- package/src/server/plugins/engine/views/components/yesnofield.html +5 -0
- package/src/server/plugins/engine/views/file-upload.html +45 -0
- package/src/server/plugins/engine/views/index.html +39 -0
- package/src/server/plugins/engine/views/item-delete.html +56 -0
- package/src/server/plugins/engine/views/partials/components.html +6 -0
- package/src/server/plugins/engine/views/partials/conditional-components.html +3 -0
- package/src/server/plugins/engine/views/partials/debug.html +44 -0
- package/src/server/plugins/engine/views/partials/form.html +15 -0
- package/src/server/plugins/engine/views/partials/heading.html +16 -0
- package/src/server/plugins/engine/views/partials/preview-banner.html +32 -0
- package/src/server/plugins/engine/views/partials/preview-banner.test.js +122 -0
- package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
- package/src/server/plugins/engine/views/repeat-list-summary.html +53 -0
- package/src/server/plugins/errorPages.ts +58 -0
- package/src/server/plugins/nunjucks/context.js +88 -0
- package/src/server/plugins/nunjucks/context.test.js +142 -0
- package/src/server/plugins/nunjucks/enviroment.test.js +201 -0
- package/src/server/plugins/nunjucks/environment.js +116 -0
- package/src/server/plugins/nunjucks/filters/answer.js +27 -0
- package/src/server/plugins/nunjucks/filters/answer.test.js +89 -0
- package/src/server/plugins/nunjucks/filters/evaluate.js +21 -0
- package/src/server/plugins/nunjucks/filters/field.js +28 -0
- package/src/server/plugins/nunjucks/filters/field.test.js +75 -0
- package/src/server/plugins/nunjucks/filters/highlight.js +11 -0
- package/src/server/plugins/nunjucks/filters/href.js +30 -0
- package/src/server/plugins/nunjucks/filters/href.test.js +80 -0
- package/src/server/plugins/nunjucks/filters/index.js +8 -0
- package/src/server/plugins/nunjucks/filters/inspect.js +15 -0
- package/src/server/plugins/nunjucks/filters/page.js +24 -0
- package/src/server/plugins/nunjucks/filters/page.test.js +65 -0
- package/src/server/plugins/nunjucks/index.js +3 -0
- package/src/server/plugins/nunjucks/plugin.js +40 -0
- package/src/server/plugins/nunjucks/render.js +42 -0
- package/src/server/plugins/nunjucks/types.js +40 -0
- package/src/server/plugins/pulse.ts +11 -0
- package/src/server/plugins/router.ts +201 -0
- package/src/server/plugins/session.ts +28 -0
- package/src/server/routes/health.js +13 -0
- package/src/server/routes/health.test.js +35 -0
- package/src/server/routes/index.test.ts +125 -0
- package/src/server/routes/index.ts +2 -0
- package/src/server/routes/public.ts +47 -0
- package/src/server/routes/types.ts +48 -0
- package/src/server/schemas/index.ts +34 -0
- package/src/server/secure-context.js +43 -0
- package/src/server/services/cacheService.test.ts +276 -0
- package/src/server/services/cacheService.ts +131 -0
- package/src/server/services/httpService.test.js +491 -0
- package/src/server/services/httpService.ts +50 -0
- package/src/server/services/index.ts +1 -0
- package/src/server/types.ts +54 -0
- package/src/server/utils/notify.test.ts +37 -0
- package/src/server/utils/notify.ts +50 -0
- package/src/server/utils/secure-context/get-trust-store-certs.js +11 -0
- package/src/server/utils/secure-context/get-trust-store-certs.test.js +19 -0
- package/src/server/utils/utils.js +24 -0
- package/src/server/utils/utils.test.js +54 -0
- package/src/server/views/404.html +16 -0
- package/src/server/views/500.html +19 -0
- package/src/server/views/components/debug/macro.njk +3 -0
- package/src/server/views/components/debug/template.njk +13 -0
- package/src/server/views/components/service-banner/macro.njk +3 -0
- package/src/server/views/components/service-banner/template.njk +20 -0
- package/src/server/views/components/service-banner/template.test.js +43 -0
- package/src/server/views/components/tag-env/macro.njk +3 -0
- package/src/server/views/components/tag-env/template.njk +30 -0
- package/src/server/views/components/tag-env/template.test.js +66 -0
- package/src/server/views/confirmation.html +19 -0
- package/src/server/views/help/accessibility-statement.html +58 -0
- package/src/server/views/help/cookie-preferences.html +57 -0
- package/src/server/views/help/cookies.html +71 -0
- package/src/server/views/help/get-support.html +37 -0
- package/src/server/views/help/privacy-notice.html +68 -0
- package/src/server/views/help/terms-and-conditions.html +83 -0
- package/src/server/views/layout.html +199 -0
- package/src/server/views/summary.html +50 -0
- package/src/typings/hapi/index.d.ts +95 -0
- package/src/typings/hapi-tracing/index.d.ts +6 -0
- package/src/typings/index.d.ts +3 -0
- package/src/typings/joi/index.d.ts +22 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
2
|
+
import { type FormContext } from '~/src/server/plugins/engine/types.js'
|
|
3
|
+
import { type FormRequest } from '~/src/server/routes/types.js'
|
|
4
|
+
|
|
5
|
+
export class StartPageController extends QuestionPageController {
|
|
6
|
+
/**
|
|
7
|
+
* The controller which is used when Page["controller"] is defined as "./pages/start.js"
|
|
8
|
+
* This page should not be used in production. This page is helpful for prototyping start pages within the app,
|
|
9
|
+
* but start pages should really live on gov.uk (whitehall publisher) so a user can be properly signposted.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
getViewModel(request: FormRequest, context: FormContext) {
|
|
13
|
+
return {
|
|
14
|
+
...super.getViewModel(request, context),
|
|
15
|
+
isStartPage: true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type PageStatus } from '@defra/forms-model'
|
|
2
|
+
import { type ResponseToolkit } from '@hapi/hapi'
|
|
3
|
+
|
|
4
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
5
|
+
import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
6
|
+
import { type FormContext } from '~/src/server/plugins/engine/types.js'
|
|
7
|
+
import { type FormRequest } from '~/src/server/routes/types.js'
|
|
8
|
+
|
|
9
|
+
export class StatusPageController extends QuestionPageController {
|
|
10
|
+
declare pageDef: PageStatus
|
|
11
|
+
|
|
12
|
+
constructor(model: FormModel, pageDef: PageStatus) {
|
|
13
|
+
super(model, pageDef)
|
|
14
|
+
this.viewName = 'confirmation'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getRelevantPath() {
|
|
18
|
+
return this.getStatusPath()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
makeGetRouteHandler() {
|
|
22
|
+
return async (
|
|
23
|
+
request: FormRequest,
|
|
24
|
+
context: FormContext,
|
|
25
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
26
|
+
) => {
|
|
27
|
+
const { viewModel, viewName } = this
|
|
28
|
+
|
|
29
|
+
const { cacheService } = request.services([])
|
|
30
|
+
const confirmationState = await cacheService.getConfirmationState(request)
|
|
31
|
+
|
|
32
|
+
// If there's no confirmation state, then
|
|
33
|
+
// redirect the user back to the start of the form
|
|
34
|
+
if (!confirmationState.confirmed) {
|
|
35
|
+
return this.proceed(request, h, this.getStartPath())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const slug = request.params.slug
|
|
39
|
+
const { formsService } = this.model.services
|
|
40
|
+
const { getFormMetadata } = formsService
|
|
41
|
+
|
|
42
|
+
const { submissionGuidance } = await getFormMetadata(slug)
|
|
43
|
+
|
|
44
|
+
return h.view(viewName, {
|
|
45
|
+
...viewModel,
|
|
46
|
+
submissionGuidance
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { type PageSummary, type SubmitPayload } from '@defra/forms-model'
|
|
2
|
+
import Boom from '@hapi/boom'
|
|
3
|
+
import { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'
|
|
4
|
+
|
|
5
|
+
import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
6
|
+
import { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'
|
|
7
|
+
import {
|
|
8
|
+
checkEmailAddressForLiveFormSubmission,
|
|
9
|
+
checkFormStatus
|
|
10
|
+
} from '~/src/server/plugins/engine/helpers.js'
|
|
11
|
+
import {
|
|
12
|
+
SummaryViewModel,
|
|
13
|
+
type FormModel
|
|
14
|
+
} from '~/src/server/plugins/engine/models/index.js'
|
|
15
|
+
import {
|
|
16
|
+
type Detail,
|
|
17
|
+
type DetailItem
|
|
18
|
+
} from '~/src/server/plugins/engine/models/types.js'
|
|
19
|
+
import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
20
|
+
import {
|
|
21
|
+
type FormContext,
|
|
22
|
+
type FormContextRequest,
|
|
23
|
+
type FormSubmissionState
|
|
24
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
25
|
+
import {
|
|
26
|
+
type FormRequest,
|
|
27
|
+
type FormRequestPayload,
|
|
28
|
+
type FormRequestPayloadRefs
|
|
29
|
+
} from '~/src/server/routes/types.js'
|
|
30
|
+
|
|
31
|
+
export class SummaryPageController extends QuestionPageController {
|
|
32
|
+
declare pageDef: PageSummary
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The controller which is used when Page["controller"] is defined as "./pages/summary.js"
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
constructor(model: FormModel, pageDef: PageSummary) {
|
|
39
|
+
super(model, pageDef)
|
|
40
|
+
this.viewName = 'summary'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getSummaryViewModel(
|
|
44
|
+
request: FormContextRequest,
|
|
45
|
+
context: FormContext
|
|
46
|
+
): SummaryViewModel {
|
|
47
|
+
const viewModel = new SummaryViewModel(request, this, context)
|
|
48
|
+
|
|
49
|
+
// We already figure these out in the base page controller. Take them and apply them to our page-specific model.
|
|
50
|
+
// This is a stop-gap until we can add proper inheritance in place.
|
|
51
|
+
viewModel.backLink = this.getBackLink(request, context)
|
|
52
|
+
viewModel.feedbackLink = this.feedbackLink
|
|
53
|
+
viewModel.phaseTag = this.phaseTag
|
|
54
|
+
|
|
55
|
+
return viewModel
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,
|
|
60
|
+
*/
|
|
61
|
+
makeGetRouteHandler() {
|
|
62
|
+
return async (
|
|
63
|
+
request: FormRequest,
|
|
64
|
+
context: FormContext,
|
|
65
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
66
|
+
) => {
|
|
67
|
+
const { viewName } = this
|
|
68
|
+
|
|
69
|
+
const viewModel = this.getSummaryViewModel(request, context)
|
|
70
|
+
|
|
71
|
+
viewModel.hasMissingNotificationEmail =
|
|
72
|
+
await this.hasMissingNotificationEmail(request, context)
|
|
73
|
+
|
|
74
|
+
return h.view(viewName, viewModel)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.
|
|
80
|
+
* If a form is incomplete, a user will be redirected to the start page.
|
|
81
|
+
*/
|
|
82
|
+
makePostRouteHandler() {
|
|
83
|
+
return async (
|
|
84
|
+
request: FormRequestPayload,
|
|
85
|
+
context: FormContext,
|
|
86
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
87
|
+
) => {
|
|
88
|
+
const { model } = this
|
|
89
|
+
const { params } = request
|
|
90
|
+
const { state } = context
|
|
91
|
+
|
|
92
|
+
const { cacheService } = request.services([])
|
|
93
|
+
const { formsService } = this.model.services
|
|
94
|
+
const { getFormMetadata } = formsService
|
|
95
|
+
|
|
96
|
+
// Get the form metadata using the `slug` param
|
|
97
|
+
const { notificationEmail } = await getFormMetadata(params.slug)
|
|
98
|
+
const { isPreview } = checkFormStatus(request.path)
|
|
99
|
+
const emailAddress = notificationEmail ?? this.model.def.outputEmail
|
|
100
|
+
|
|
101
|
+
checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)
|
|
102
|
+
|
|
103
|
+
// Send submission email
|
|
104
|
+
if (emailAddress) {
|
|
105
|
+
const viewModel = this.getSummaryViewModel(request, context)
|
|
106
|
+
await submitForm(request, viewModel, model, state, emailAddress)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await cacheService.setConfirmationState(request, { confirmed: true })
|
|
110
|
+
|
|
111
|
+
// Clear all form data
|
|
112
|
+
await cacheService.clearState(request)
|
|
113
|
+
|
|
114
|
+
return this.proceed(request, h, this.getStatusPath())
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {
|
|
119
|
+
return {
|
|
120
|
+
ext: {
|
|
121
|
+
onPreHandler: {
|
|
122
|
+
method(request, h) {
|
|
123
|
+
return h.continue
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function submitForm(
|
|
132
|
+
request: FormRequestPayload,
|
|
133
|
+
summaryViewModel: SummaryViewModel,
|
|
134
|
+
model: FormModel,
|
|
135
|
+
state: FormSubmissionState,
|
|
136
|
+
emailAddress: string
|
|
137
|
+
) {
|
|
138
|
+
await extendFileRetention(model, state, emailAddress)
|
|
139
|
+
|
|
140
|
+
const { path } = request
|
|
141
|
+
const formStatus = checkFormStatus(path)
|
|
142
|
+
const logTags = ['submit', 'submissionApi']
|
|
143
|
+
|
|
144
|
+
request.logger.info(logTags, 'Preparing email', formStatus)
|
|
145
|
+
|
|
146
|
+
// Get detail items
|
|
147
|
+
const items = getFormSubmissionData(
|
|
148
|
+
summaryViewModel.context,
|
|
149
|
+
summaryViewModel.details
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// Submit data
|
|
153
|
+
request.logger.info(logTags, 'Submitting data')
|
|
154
|
+
const submitResponse = await submitData(
|
|
155
|
+
model,
|
|
156
|
+
items,
|
|
157
|
+
emailAddress,
|
|
158
|
+
request.yar.id
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (submitResponse === undefined) {
|
|
162
|
+
throw Boom.badRequest('Unexpected empty response from submit api')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return model.services.outputService.submit(
|
|
166
|
+
request,
|
|
167
|
+
model,
|
|
168
|
+
emailAddress,
|
|
169
|
+
items,
|
|
170
|
+
submitResponse
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function extendFileRetention(
|
|
175
|
+
model: FormModel,
|
|
176
|
+
state: FormSubmissionState,
|
|
177
|
+
updatedRetrievalKey: string
|
|
178
|
+
) {
|
|
179
|
+
const { formSubmissionService } = model.services
|
|
180
|
+
const { persistFiles } = formSubmissionService
|
|
181
|
+
const files: { fileId: string; initiatedRetrievalKey: string }[] = []
|
|
182
|
+
|
|
183
|
+
// For each file upload component with files in
|
|
184
|
+
// state, add the files to the batch getting persisted
|
|
185
|
+
model.pages.forEach((page) => {
|
|
186
|
+
const fileUploadComponents = page.collection.fields.filter(
|
|
187
|
+
(component) => component instanceof FileUploadField
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
fileUploadComponents.forEach((component) => {
|
|
191
|
+
const values = component.getFormValueFromState(state)
|
|
192
|
+
if (!values?.length) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
files.push(
|
|
197
|
+
...values.map(({ status }) => ({
|
|
198
|
+
fileId: status.form.file.fileId,
|
|
199
|
+
initiatedRetrievalKey: status.metadata.retrievalKey
|
|
200
|
+
}))
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (files.length) {
|
|
206
|
+
return persistFiles(files, updatedRetrievalKey)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function submitData(
|
|
211
|
+
model: FormModel,
|
|
212
|
+
items: DetailItem[],
|
|
213
|
+
retrievalKey: string,
|
|
214
|
+
sessionId: string
|
|
215
|
+
) {
|
|
216
|
+
const { formSubmissionService } = model.services
|
|
217
|
+
const { submit } = formSubmissionService
|
|
218
|
+
|
|
219
|
+
const payload: SubmitPayload = {
|
|
220
|
+
sessionId,
|
|
221
|
+
retrievalKey,
|
|
222
|
+
|
|
223
|
+
// Main form answers
|
|
224
|
+
main: items
|
|
225
|
+
.filter((item) => 'field' in item)
|
|
226
|
+
.map((item) => ({
|
|
227
|
+
name: item.name,
|
|
228
|
+
title: item.label,
|
|
229
|
+
value: getAnswer(item.field, item.state, { format: 'data' })
|
|
230
|
+
})),
|
|
231
|
+
|
|
232
|
+
// Repeater form answers
|
|
233
|
+
repeaters: items
|
|
234
|
+
.filter((item) => 'subItems' in item)
|
|
235
|
+
.map((item) => ({
|
|
236
|
+
name: item.name,
|
|
237
|
+
title: item.label,
|
|
238
|
+
|
|
239
|
+
// Repeater item values
|
|
240
|
+
value: item.subItems.map((detailItems) =>
|
|
241
|
+
detailItems.map((subItem) => ({
|
|
242
|
+
name: subItem.name,
|
|
243
|
+
title: subItem.label,
|
|
244
|
+
value: getAnswer(subItem.field, subItem.state, { format: 'data' })
|
|
245
|
+
}))
|
|
246
|
+
)
|
|
247
|
+
}))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return submit(payload)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function getFormSubmissionData(context: FormContext, details: Detail[]) {
|
|
254
|
+
return context.relevantPages
|
|
255
|
+
.map(({ href }) =>
|
|
256
|
+
details.flatMap(({ items }) =>
|
|
257
|
+
items.filter(({ page }) => page.href === href)
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
.flat()
|
|
261
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
2
|
+
import { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/TerminalPageController.js'
|
|
3
|
+
import definition from '~/test/form/definitions/basic.js'
|
|
4
|
+
|
|
5
|
+
describe('TerminalController', () => {
|
|
6
|
+
let model: FormModel
|
|
7
|
+
let controller1: TerminalPageController
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
const { pages } = definition
|
|
11
|
+
|
|
12
|
+
const page1 = pages[0]
|
|
13
|
+
|
|
14
|
+
model = new FormModel(definition, {
|
|
15
|
+
basePath: 'test'
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
controller1 = new TerminalPageController(model, page1)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('Route handlers', () => {
|
|
22
|
+
it('does not support POST route handler', () => {
|
|
23
|
+
expect(() => controller1.makePostRouteHandler()).toThrow(
|
|
24
|
+
'POST method not allowed for terminal pages'
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PageTerminal } from '@defra/forms-model'
|
|
2
|
+
import Boom from '@hapi/boom'
|
|
3
|
+
import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'
|
|
4
|
+
|
|
5
|
+
import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
6
|
+
import { type FormContext } from '~/src/server/plugins/engine/types.js'
|
|
7
|
+
import { type FormRequestPayload } from '~/src/server/routes/types.js'
|
|
8
|
+
|
|
9
|
+
export class TerminalPageController extends QuestionPageController {
|
|
10
|
+
declare pageDef: PageTerminal
|
|
11
|
+
|
|
12
|
+
makePostRouteHandler(): (
|
|
13
|
+
request: FormRequestPayload,
|
|
14
|
+
context: FormContext,
|
|
15
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
16
|
+
) => Promise<ResponseObject> {
|
|
17
|
+
throw Boom.methodNotAllowed('POST method not allowed for terminal pages')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentType,
|
|
3
|
+
ControllerType,
|
|
4
|
+
ControllerTypes,
|
|
5
|
+
PageTypes,
|
|
6
|
+
type Page
|
|
7
|
+
} from '@defra/forms-model'
|
|
8
|
+
|
|
9
|
+
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
10
|
+
import { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
11
|
+
import {
|
|
12
|
+
createPage,
|
|
13
|
+
getProxyUrlForLocalDevelopment,
|
|
14
|
+
isPageController,
|
|
15
|
+
type PageControllerType
|
|
16
|
+
} from '~/src/server/plugins/engine/pageControllers/helpers.js'
|
|
17
|
+
import {
|
|
18
|
+
FileUploadPageController,
|
|
19
|
+
QuestionPageController,
|
|
20
|
+
RepeatPageController,
|
|
21
|
+
StartPageController,
|
|
22
|
+
StatusPageController,
|
|
23
|
+
SummaryPageController,
|
|
24
|
+
TerminalPageController
|
|
25
|
+
} from '~/src/server/plugins/engine/pageControllers/index.js'
|
|
26
|
+
import definition from '~/test/form/definitions/blank.js'
|
|
27
|
+
|
|
28
|
+
describe('Page controller helpers', () => {
|
|
29
|
+
const examples = PageTypes.map((pageType) => {
|
|
30
|
+
const pageDef = structuredClone(pageType)
|
|
31
|
+
|
|
32
|
+
let controller: PageControllerType | undefined
|
|
33
|
+
|
|
34
|
+
switch (pageDef.controller) {
|
|
35
|
+
case ControllerType.Start:
|
|
36
|
+
controller = StartPageController
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
case ControllerType.Page:
|
|
40
|
+
controller = QuestionPageController
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
case ControllerType.Terminal:
|
|
44
|
+
controller = TerminalPageController
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
case ControllerType.Repeat:
|
|
48
|
+
controller = RepeatPageController
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
case ControllerType.FileUpload:
|
|
52
|
+
controller = FileUploadPageController
|
|
53
|
+
|
|
54
|
+
// Avoid minimum component requirements
|
|
55
|
+
pageDef.components = [
|
|
56
|
+
{
|
|
57
|
+
type: ComponentType.FileUploadField,
|
|
58
|
+
name: 'fileUpload',
|
|
59
|
+
title: 'Upload something',
|
|
60
|
+
options: {},
|
|
61
|
+
schema: {}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
case ControllerType.Summary:
|
|
67
|
+
controller = SummaryPageController
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
case ControllerType.Status:
|
|
71
|
+
controller = StatusPageController
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
controller,
|
|
77
|
+
pageDef
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('Helper: createPage', () => {
|
|
82
|
+
const model = new FormModel(definition, {
|
|
83
|
+
basePath: 'test'
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it.each([...examples])(
|
|
87
|
+
"creates page for controller '$pageDef.controller'",
|
|
88
|
+
({ controller, pageDef }) => {
|
|
89
|
+
const pageDef1 = structuredClone(pageDef)
|
|
90
|
+
const pageDef2 = structuredClone(pageDef)
|
|
91
|
+
|
|
92
|
+
expect(createPage(model, pageDef1)).toBeInstanceOf(controller)
|
|
93
|
+
|
|
94
|
+
const controllerType = ControllerTypes.find(
|
|
95
|
+
({ name }) => name === pageDef1.controller
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// @ts-expect-error - Allow invalid property for test
|
|
99
|
+
pageDef2.controller = controllerType?.path
|
|
100
|
+
|
|
101
|
+
// Check for legacy path support
|
|
102
|
+
expect(createPage(model, pageDef2)).toBeInstanceOf(controller)
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
describe('Helper: createPage (custom controllers)', () => {
|
|
107
|
+
it('uses custom controllers from model.controllers', () => {
|
|
108
|
+
class CustomPageController extends PageController {
|
|
109
|
+
customProperty: string
|
|
110
|
+
|
|
111
|
+
constructor(model: FormModel, pageDef: Page) {
|
|
112
|
+
super(model, pageDef)
|
|
113
|
+
this.customProperty = 'some-value'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const customPageDef = {
|
|
118
|
+
path: '/custom-page',
|
|
119
|
+
title: 'Custom Page',
|
|
120
|
+
controller: 'CustomController',
|
|
121
|
+
components: []
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const testModel = new FormModel(definition, {
|
|
125
|
+
basePath: 'test'
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
type ControllerMap = Record<string, typeof PageController>
|
|
129
|
+
testModel.controllers = {
|
|
130
|
+
CustomController: CustomPageController
|
|
131
|
+
} as ControllerMap
|
|
132
|
+
|
|
133
|
+
const controller = createPage(testModel, customPageDef as Page)
|
|
134
|
+
|
|
135
|
+
expect(controller).toBeInstanceOf(CustomPageController)
|
|
136
|
+
expect(controller).toHaveProperty('customProperty', 'some-value')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('throws if page controller is unknown', () => {
|
|
141
|
+
const pageDef: Page = {
|
|
142
|
+
title: 'Unknown page',
|
|
143
|
+
path: '/unknown-page',
|
|
144
|
+
// @ts-expect-error - Allow invalid property for test
|
|
145
|
+
controller: 'UnknownPageController'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
expect(() => createPage(model, pageDef)).toThrow(
|
|
149
|
+
`Page controller ${pageDef.controller} does not exist`
|
|
150
|
+
)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe('Helper: isPageController', () => {
|
|
155
|
+
it.each([...examples])(
|
|
156
|
+
"allows valid page controller '$pageDef.controller'",
|
|
157
|
+
({ pageDef }) => {
|
|
158
|
+
expect(isPageController(pageDef.controller)).toBe(true)
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
it.each([
|
|
163
|
+
{ name: './pages/unknown.js' },
|
|
164
|
+
{ name: 'UnknownPageController' },
|
|
165
|
+
{ name: undefined },
|
|
166
|
+
{ name: '' }
|
|
167
|
+
])("rejects invalid page controller '$name'", ({ name }) => {
|
|
168
|
+
expect(isPageController(name)).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe('Helper: getProxyUrlForLocalDevelopment', () => {
|
|
173
|
+
it('returns null if uploadUrl is undefined', () => {
|
|
174
|
+
expect(getProxyUrlForLocalDevelopment(undefined)).toBeNull()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('returns null if uploadUrl does not include localhost:7337', () => {
|
|
178
|
+
expect(getProxyUrlForLocalDevelopment('https://some-url.com')).toBeNull()
|
|
179
|
+
expect(getProxyUrlForLocalDevelopment('http://localhost:8080')).toBeNull()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('replaces localhost:7337 with uploader.127.0.0.1.sslip.io:7300', () => {
|
|
183
|
+
const originalUrl = 'http://localhost:7337/upload'
|
|
184
|
+
const expectedUrl = 'http://uploader.127.0.0.1.sslip.io:7300/upload'
|
|
185
|
+
|
|
186
|
+
expect(getProxyUrlForLocalDevelopment(originalUrl)).toBe(expectedUrl)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('handles multiple occurrences of localhost:7337', () => {
|
|
190
|
+
const originalUrl =
|
|
191
|
+
'http://localhost:7337/path?redirect=http://localhost:7337/callback'
|
|
192
|
+
const expectedUrl =
|
|
193
|
+
'http://uploader.127.0.0.1.sslip.io:7300/path?redirect=http://uploader.127.0.0.1.sslip.io:7300/callback'
|
|
194
|
+
|
|
195
|
+
expect(getProxyUrlForLocalDevelopment(originalUrl)).toBe(expectedUrl)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ControllerType,
|
|
3
|
+
controllerNameFromPath,
|
|
4
|
+
isControllerName,
|
|
5
|
+
type Page
|
|
6
|
+
} from '@defra/forms-model'
|
|
7
|
+
|
|
8
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
9
|
+
import * as PageControllers from '~/src/server/plugins/engine/pageControllers/index.js'
|
|
10
|
+
|
|
11
|
+
export function isPageController(
|
|
12
|
+
controllerName?: string | ControllerType
|
|
13
|
+
): controllerName is keyof typeof PageControllers {
|
|
14
|
+
return isControllerName(controllerName) && controllerName in PageControllers
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type PageControllerClass = InstanceType<PageControllerType>
|
|
18
|
+
export type PageControllerType =
|
|
19
|
+
(typeof PageControllers)[keyof typeof PageControllers]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates page instance for each {@link Page} type
|
|
23
|
+
*/
|
|
24
|
+
export function createPage(model: FormModel, pageDef: Page) {
|
|
25
|
+
const controllerName = controllerNameFromPath(pageDef.controller)
|
|
26
|
+
|
|
27
|
+
if (!pageDef.controller) {
|
|
28
|
+
return new PageControllers.QuestionPageController(model, pageDef)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Patch legacy controllers
|
|
32
|
+
if (controllerName && pageDef.controller !== controllerName) {
|
|
33
|
+
pageDef.controller = controllerName
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let controller: PageControllerClass | undefined
|
|
37
|
+
|
|
38
|
+
switch (pageDef.controller) {
|
|
39
|
+
case ControllerType.Start:
|
|
40
|
+
controller = new PageControllers.StartPageController(model, pageDef)
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
case ControllerType.Page:
|
|
44
|
+
controller = new PageControllers.QuestionPageController(model, pageDef)
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
case ControllerType.Terminal:
|
|
48
|
+
controller = new PageControllers.TerminalPageController(model, pageDef)
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
case ControllerType.Summary:
|
|
52
|
+
controller = new PageControllers.SummaryPageController(model, pageDef)
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
case ControllerType.Status:
|
|
56
|
+
controller = new PageControllers.StatusPageController(model, pageDef)
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
case ControllerType.FileUpload:
|
|
60
|
+
controller = new PageControllers.FileUploadPageController(model, pageDef)
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
case ControllerType.Repeat:
|
|
64
|
+
controller = new PageControllers.RepeatPageController(model, pageDef)
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof controller === 'undefined') {
|
|
69
|
+
if (model.controllers?.[pageDef.controller]) {
|
|
70
|
+
const Ctrl = model.controllers[pageDef.controller]
|
|
71
|
+
controller = new Ctrl(model, pageDef) as unknown as PageControllerClass // type assertion needed because TS can't verify custom controller structure
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof controller === 'undefined') {
|
|
76
|
+
throw new Error(`Page controller ${pageDef.controller} does not exist`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return controller
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* In local development environments, we sometimes need to rewrite the
|
|
84
|
+
* CDP upload URL to work with CSP/CORS restrictions.
|
|
85
|
+
* This helper function rewrites localhost URLs to use the sslip.io proxy
|
|
86
|
+
* This is only used when running locally with a development proxy.
|
|
87
|
+
* In non-local environments, this function returns null
|
|
88
|
+
* @param uploadUrl - The original upload URL from CDP
|
|
89
|
+
*/
|
|
90
|
+
export function getProxyUrlForLocalDevelopment(
|
|
91
|
+
uploadUrl?: string
|
|
92
|
+
): string | null {
|
|
93
|
+
if (!uploadUrl?.includes('localhost:7337')) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return uploadUrl.replace(
|
|
98
|
+
/localhost:7337/g,
|
|
99
|
+
'uploader.127.0.0.1.sslip.io:7300'
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { StartPageController } from '~/src/server/plugins/engine/pageControllers/StartPageController.js'
|
|
2
|
+
export {
|
|
3
|
+
QuestionPageController, // Export alongside alias
|
|
4
|
+
QuestionPageController as PageController
|
|
5
|
+
} from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
6
|
+
export { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/TerminalPageController.js'
|
|
7
|
+
export { SummaryPageController } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
|
|
8
|
+
export { StatusPageController } from '~/src/server/plugins/engine/pageControllers/StatusPageController.js'
|
|
9
|
+
export { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
|
|
10
|
+
export { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
|