@gov-cy/govcy-express-services 0.1.1
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/LICENSE +21 -0
- package/README.md +1157 -0
- package/package.json +72 -0
- package/src/auth/cyLoginAuth.mjs +123 -0
- package/src/index.mjs +188 -0
- package/src/middleware/cyLoginAuth.mjs +131 -0
- package/src/middleware/govcyConfigSiteData.mjs +38 -0
- package/src/middleware/govcyCsrf.mjs +36 -0
- package/src/middleware/govcyFormsPostHandler.mjs +83 -0
- package/src/middleware/govcyHeadersControl.mjs +22 -0
- package/src/middleware/govcyHttpErrorHandler.mjs +63 -0
- package/src/middleware/govcyLanguageMiddleware.mjs +19 -0
- package/src/middleware/govcyLogger.mjs +15 -0
- package/src/middleware/govcyManifestHandler.mjs +46 -0
- package/src/middleware/govcyPDFRender.mjs +30 -0
- package/src/middleware/govcyPageHandler.mjs +110 -0
- package/src/middleware/govcyPageRender.mjs +14 -0
- package/src/middleware/govcyRequestTimer.mjs +29 -0
- package/src/middleware/govcyReviewPageHandler.mjs +102 -0
- package/src/middleware/govcyReviewPostHandler.mjs +147 -0
- package/src/middleware/govcyRoutePageHandler.mjs +37 -0
- package/src/middleware/govcyServiceEligibilityHandler.mjs +101 -0
- package/src/middleware/govcySessionData.mjs +9 -0
- package/src/middleware/govcySuccessPageHandler.mjs +112 -0
- package/src/public/img/Certificate_A4.svg +30 -0
- package/src/public/js/govcyForms.js +21 -0
- package/src/resources/govcyResources.mjs +430 -0
- package/src/standalone.mjs +7 -0
- package/src/utils/govcyApiRequest.mjs +114 -0
- package/src/utils/govcyConstants.mjs +4 -0
- package/src/utils/govcyDataLayer.mjs +311 -0
- package/src/utils/govcyEnvVariables.mjs +45 -0
- package/src/utils/govcyFormHandling.mjs +148 -0
- package/src/utils/govcyLoadConfigData.mjs +135 -0
- package/src/utils/govcyLogger.mjs +30 -0
- package/src/utils/govcyNotification.mjs +85 -0
- package/src/utils/govcyPdfMaker.mjs +27 -0
- package/src/utils/govcyReviewSummary.mjs +205 -0
- package/src/utils/govcySubmitData.mjs +530 -0
- package/src/utils/govcyUtils.mjs +13 -0
- package/src/utils/govcyValidator.mjs +352 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to set security and cache control headers for GovCy API responses.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} req The request object
|
|
5
|
+
* @param {object} res The response object
|
|
6
|
+
* @param {object} next The next middleware function
|
|
7
|
+
*/
|
|
8
|
+
export function noCacheAndSecurityHeaders(req, res, next) {
|
|
9
|
+
//set security headers
|
|
10
|
+
res.set({
|
|
11
|
+
'X-Content-Type-Options': 'nosniff', //Prevents browsers from MIME-sniffing a response away from the declared Content-Type
|
|
12
|
+
'X-Frame-Options': 'SAMEORIGIN', //Prevents clickjacking by disallowing the site from being embedded in an <iframe>, unless from the same origin
|
|
13
|
+
'X-XSS-Protection': '1; mode=block', // Optional - only if you really want to support old IE versions
|
|
14
|
+
'Content-Security-Policy': "frame-ancestors 'self'", // Modern approach to control what domains are allowed to embed your site in an iframe.
|
|
15
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', // Forces browsers to use HTTPS only, even for future visits.
|
|
16
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin'
|
|
17
|
+
});
|
|
18
|
+
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0');
|
|
19
|
+
res.set("Pragma", "no-cache");
|
|
20
|
+
res.set("Expires", "0");
|
|
21
|
+
next();
|
|
22
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { govcyFrontendRenderer } from '@gov-cy/govcy-frontend-renderer';
|
|
2
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
3
|
+
import * as dataLayer from "../utils/govcyDataLayer.mjs";
|
|
4
|
+
import { logger } from "../utils/govcyLogger.mjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Middleware function to handle HTTP errors and render appropriate error pages.
|
|
8
|
+
* This function captures errors that occur during the request lifecycle and generates appropriate error pages based on the error status code.
|
|
9
|
+
*/
|
|
10
|
+
export function govcyHttpErrorHandler(err, req, res, next) {
|
|
11
|
+
|
|
12
|
+
logger.debug("HTTP Error details:", err, req); // Log the error details
|
|
13
|
+
// Set default status and message
|
|
14
|
+
let statusCode = err.status || 500;
|
|
15
|
+
let message = err.message || "Internal Server Error";
|
|
16
|
+
|
|
17
|
+
// Deep copy renderer pageData from
|
|
18
|
+
let pageData = JSON.parse(JSON.stringify(govcyResources.staticResources.rendererPageData));
|
|
19
|
+
|
|
20
|
+
// Handle specific HTTP errors
|
|
21
|
+
switch (statusCode) {
|
|
22
|
+
case 404:
|
|
23
|
+
logger.info("404 - Page not found.", err.message, req.originalUrl); // Log the error
|
|
24
|
+
pageData.pageData.title = govcyResources.staticResources.text.errorPage404Title;
|
|
25
|
+
pageData.pageData.text = govcyResources.staticResources.text.errorPage404Body;
|
|
26
|
+
message = "404 - Page not found.";
|
|
27
|
+
break;
|
|
28
|
+
case 403:
|
|
29
|
+
logger.warn("HTTP Error:", err.message, req.originalUrl); // Log the error
|
|
30
|
+
pageData.pageData.title = govcyResources.staticResources.text.errorPage403Title;
|
|
31
|
+
if (err.message === "Access Denied: natural person policy not met.") {
|
|
32
|
+
pageData.pageData.text = govcyResources.staticResources.text.errorPage403NaturalOnlyPolicyBody;
|
|
33
|
+
} else {
|
|
34
|
+
pageData.pageData.text = govcyResources.staticResources.text.errorPage403Body;
|
|
35
|
+
}
|
|
36
|
+
message = "403 - Forbidden access.";
|
|
37
|
+
break;
|
|
38
|
+
case 500:
|
|
39
|
+
logger.error("HTTP Error:", err.message, req.originalUrl); // Log the error
|
|
40
|
+
pageData.pageData.title = govcyResources.staticResources.text.errorPage500Title;
|
|
41
|
+
pageData.pageData.text = govcyResources.staticResources.text.errorPage500Body;
|
|
42
|
+
message = "500 - Something went wrong on our end.";
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
res.status(statusCode);
|
|
47
|
+
|
|
48
|
+
// Return JSON if the request expects it
|
|
49
|
+
if (req.headers.accept && req.headers.accept.includes("application/json")) {
|
|
50
|
+
return res.json({ error: message });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Render an error page for non-JSON requests
|
|
54
|
+
const renderer = new govcyFrontendRenderer();
|
|
55
|
+
let pageTemplate = govcyResources.simpleHtmlPageTemplate(pageData.pageData.title, pageData.pageData.text);
|
|
56
|
+
pageData.site.lang = req.globalLang; //use lang from middleware
|
|
57
|
+
//if user is logged in add he user bane section in the page template
|
|
58
|
+
if (dataLayer.getUser(req.session)) {
|
|
59
|
+
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
60
|
+
}
|
|
61
|
+
const html = renderer.renderFromJSON(pageTemplate, pageData);
|
|
62
|
+
res.send(html);
|
|
63
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to set the language for the GovCy application.
|
|
3
|
+
* It checks the query parameter and cookie for the language setting.
|
|
4
|
+
*/
|
|
5
|
+
export function govcyLanguageMiddleware(req, res, next) {
|
|
6
|
+
let lang = req.query.lang || req.cookies.lang || 'el'; // Default to 'en' if not set
|
|
7
|
+
|
|
8
|
+
// let lang = req.query.lang
|
|
9
|
+
|
|
10
|
+
if (req.query.lang) {
|
|
11
|
+
res.cookie('lang', lang, {
|
|
12
|
+
maxAge: 365 * 24 * 60 * 60 * 1000,
|
|
13
|
+
httpOnly: true,
|
|
14
|
+
sameSite: 'lax' });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
req.globalLang = lang; // Store language for request lifecycle
|
|
18
|
+
next();
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { logger } from '../utils/govcyLogger.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Middleware to log incoming requests.
|
|
5
|
+
*/
|
|
6
|
+
export function requestLogger(req, res, next) {
|
|
7
|
+
const timestamp = new Date().toISOString();
|
|
8
|
+
logger.info(`[${timestamp}]`, req.method, req.url);
|
|
9
|
+
|
|
10
|
+
if (req.session) {
|
|
11
|
+
logger.info(`Session ID: ${req.sessionID}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
next(); // Pass control to the next middleware
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to handle rendering of manifest.json
|
|
3
|
+
*/
|
|
4
|
+
export function govcyManifestHandler() {
|
|
5
|
+
return (req, res, next) => {
|
|
6
|
+
try {
|
|
7
|
+
const { site } = req.serviceData;
|
|
8
|
+
|
|
9
|
+
// Generate the manifest JSON dynamically
|
|
10
|
+
const manifest = {
|
|
11
|
+
short_name: site.title[site.lang],
|
|
12
|
+
name: site.title[site.lang],
|
|
13
|
+
description: site.description[site.lang],
|
|
14
|
+
icons: [
|
|
15
|
+
{
|
|
16
|
+
src: `${site.cdn.dist}/img/icons-128.png`,
|
|
17
|
+
type: "image/png",
|
|
18
|
+
sizes: "128x128"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
src: `${site.cdn.dist}/img/icons-192.png`,
|
|
22
|
+
type: "image/png",
|
|
23
|
+
sizes: "192x192"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
src: `${site.cdn.dist}/img/icons-512.png`,
|
|
27
|
+
type: "image/png",
|
|
28
|
+
sizes: "512x512"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
start_url: `/${req.params.siteId}/index`,
|
|
32
|
+
background_color: "#31576F",
|
|
33
|
+
display: "standalone",
|
|
34
|
+
scope: `/${req.params.siteId}/`,
|
|
35
|
+
theme_color: "#31576F",
|
|
36
|
+
dir: "ltr"
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Set the Content-Type to application/json and send the manifest
|
|
40
|
+
res.setHeader('Content-Type', 'application/json');
|
|
41
|
+
res.json(manifest);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return next(error); // Pass error to govcyHttpErrorHandler
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { govcyFrontendRenderer } from "@gov-cy/govcy-frontend-renderer";
|
|
2
|
+
import { generatePDF } from "../utils/govcyPdfMaker.mjs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Middleware function to render PDFs using the GovCy Frontend Renderer.
|
|
6
|
+
* This function takes the processed page data and template, and generates the final PDF response.
|
|
7
|
+
*/
|
|
8
|
+
export function govcyPDFRender() {
|
|
9
|
+
return async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const renderer = new govcyFrontendRenderer();
|
|
12
|
+
const { processedPage } = req;
|
|
13
|
+
const html = renderer.renderFromJSON(processedPage.pageTemplate, processedPage.pageData);
|
|
14
|
+
let fileName= "govcy.pdf";
|
|
15
|
+
if (processedPage.fileName) {
|
|
16
|
+
fileName = `${processedPage.fileName} - ${fileName}`;
|
|
17
|
+
}
|
|
18
|
+
const pdfBuffer = await generatePDF(html);
|
|
19
|
+
|
|
20
|
+
res.set({
|
|
21
|
+
'Content-Type': 'application/pdf',
|
|
22
|
+
'Content-Length': pdfBuffer.length,
|
|
23
|
+
'Content-Disposition': `attachment; filename="${fileName}"`,
|
|
24
|
+
});
|
|
25
|
+
res.send(pdfBuffer);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
res.status(500).send('Unable to generate PDF at this time.');
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getPageConfigData } from "../utils/govcyLoadConfigData.mjs";
|
|
2
|
+
import { populateFormData } from "../utils/govcyFormHandling.mjs";
|
|
3
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
4
|
+
import * as dataLayer from "../utils/govcyDataLayer.mjs";
|
|
5
|
+
import { logger } from "../utils/govcyLogger.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Middleware to handle page rendering and form processing
|
|
9
|
+
* This middleware processes the page template, populates form data, and shows validation errors.
|
|
10
|
+
*/
|
|
11
|
+
export function govcyPageHandler() {
|
|
12
|
+
return (req, res, next) => {
|
|
13
|
+
try {
|
|
14
|
+
// Extract siteId and pageUrl from request
|
|
15
|
+
let { siteId, pageUrl } = req.params;
|
|
16
|
+
|
|
17
|
+
// get service data
|
|
18
|
+
let serviceCopy = req.serviceData;
|
|
19
|
+
|
|
20
|
+
// 🏠 Handle index page: If pageUrl is undefined (meaning the user accessed `/:siteId`), set a default value or handle accordingly
|
|
21
|
+
if (!pageUrl) {
|
|
22
|
+
logger.debug(`No pageUrl provided for siteId: ${siteId}`, req);
|
|
23
|
+
// Example: Redirect to a default page or load a homepage
|
|
24
|
+
pageUrl = "index"; // Change "index" to whatever makes sense for your service
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 🔍 Find the page by pageUrl
|
|
28
|
+
const page = getPageConfigData(serviceCopy, pageUrl);
|
|
29
|
+
|
|
30
|
+
// Deep copy pageTemplate to avoid modifying the original
|
|
31
|
+
const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
|
|
32
|
+
|
|
33
|
+
//if user is logged in add he user bane section in the page template
|
|
34
|
+
if (dataLayer.getUser(req.session)) {
|
|
35
|
+
pageTemplateCopy.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
36
|
+
}
|
|
37
|
+
//⚙️ Process forms before rendering
|
|
38
|
+
pageTemplateCopy.sections.forEach(section => {
|
|
39
|
+
section.elements.forEach(element => {
|
|
40
|
+
if (element.element === "form") {
|
|
41
|
+
logger.debug("Processing form element:", element, req);
|
|
42
|
+
element.params.action = govcyResources.constructPageUrl(siteId, page.pageData.url, (req.query.route === "review" ? "review" : ""));
|
|
43
|
+
// Set form method to POST
|
|
44
|
+
element.params.method = "POST";
|
|
45
|
+
// ➕ Add CSRF token
|
|
46
|
+
element.params.elements.push(govcyResources.csrfTokenInput(req.csrfToken()));
|
|
47
|
+
element.params.elements.push(govcyResources.staticResources.elements["govcyFormsJs"]);
|
|
48
|
+
|
|
49
|
+
// 🔍 Find the first button with `prototypeNavigate`
|
|
50
|
+
const button = element.params.elements.find(subElement =>
|
|
51
|
+
// subElement.element === "button" && subElement.params.prototypeNavigate
|
|
52
|
+
subElement.element === "button"
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// ⚙️ Modify the button if it exists
|
|
56
|
+
if (button) {
|
|
57
|
+
// Store the value of `prototypeNavigate`
|
|
58
|
+
//const prototypeNavigateValue = button.params.prototypeNavigate;
|
|
59
|
+
// Remove `prototypeNavigate`
|
|
60
|
+
if (button.params.prototypeNavigate) {
|
|
61
|
+
delete button.params.prototypeNavigate;
|
|
62
|
+
}
|
|
63
|
+
// Set `type` to "submit"
|
|
64
|
+
button.params.type = "submit";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle form data
|
|
68
|
+
let theData = {};
|
|
69
|
+
|
|
70
|
+
//--------- Handle Validation Errors ---------
|
|
71
|
+
// Check if validation errors exist in the session
|
|
72
|
+
const validationErrors = dataLayer.getPageValidationErrors(req.session, siteId, pageUrl);
|
|
73
|
+
if (validationErrors ) {
|
|
74
|
+
// Populate form data from validation errors
|
|
75
|
+
theData = validationErrors?.formData || {};
|
|
76
|
+
} else {
|
|
77
|
+
// Populate form data from session
|
|
78
|
+
theData = dataLayer.getPageData(req.session, siteId, pageUrl);
|
|
79
|
+
}
|
|
80
|
+
//--------- End of Handle Validation Errors ---------
|
|
81
|
+
|
|
82
|
+
populateFormData(element.params.elements, theData,validationErrors);
|
|
83
|
+
// if there are validation errors, add an error summary
|
|
84
|
+
if (validationErrors?.errorSummary?.length > 0) {
|
|
85
|
+
element.params.elements.unshift(govcyResources.errorSummary(validationErrors.errorSummary));
|
|
86
|
+
}
|
|
87
|
+
logger.debug("Processed form element:", element, req);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Attach processed data to request
|
|
93
|
+
req.processedPage = {
|
|
94
|
+
pageData: {
|
|
95
|
+
"site": serviceCopy.site,
|
|
96
|
+
"pageData": {
|
|
97
|
+
"title": page.pageData.title,
|
|
98
|
+
"layout": page.pageData.layout,
|
|
99
|
+
"mainLayout": page.pageData.mainLayout
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
pageTemplate: pageTemplateCopy
|
|
103
|
+
};
|
|
104
|
+
logger.debug("Processed page data:", req.processedPage, req);
|
|
105
|
+
next(); // Pass control to the next middleware or route
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return next(error); // Pass error to govcyHttpErrorHandler
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { govcyFrontendRenderer } from "@gov-cy/govcy-frontend-renderer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Middleware function to render pages using the GovCy Frontend Renderer.
|
|
5
|
+
* This function takes the processed page data and template, and generates the final HTML response.
|
|
6
|
+
*/
|
|
7
|
+
export function renderGovcyPage() {
|
|
8
|
+
return (req, res) => {
|
|
9
|
+
const renderer = new govcyFrontendRenderer();
|
|
10
|
+
const { processedPage } = req;
|
|
11
|
+
const html = renderer.renderFromJSON(processedPage.pageTemplate, processedPage.pageData);
|
|
12
|
+
res.send(html);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { logger } from '../utils/govcyLogger.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Middleware to log the duration of each request.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} req The request object
|
|
7
|
+
* @param {object} res The response object
|
|
8
|
+
* @param {function} next The next middleware function
|
|
9
|
+
*/
|
|
10
|
+
export function requestTimer(req, res, next) {
|
|
11
|
+
// Record the start time of the request
|
|
12
|
+
req.startTime = Date.now();
|
|
13
|
+
|
|
14
|
+
// Listen for the 'finish' event on the response
|
|
15
|
+
res.on('finish', () => {
|
|
16
|
+
const duration = Date.now() - req.startTime; // Calculate duration
|
|
17
|
+
logger.debug('Request completed', {
|
|
18
|
+
method: req.method,
|
|
19
|
+
url: req.originalUrl,
|
|
20
|
+
duration: `${duration}ms`,
|
|
21
|
+
status: res.statusCode
|
|
22
|
+
});
|
|
23
|
+
if (duration > 500) {
|
|
24
|
+
logger.debug('[WARNING] - Slow request detected', duration );
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
next(); // Pass control to the next middleware
|
|
29
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
2
|
+
import * as dataLayer from "../utils/govcyDataLayer.mjs";
|
|
3
|
+
import { logger } from "../utils/govcyLogger.mjs";
|
|
4
|
+
import {preparePrintFriendlyData , generateReviewSummary } from "../utils/govcySubmitData.mjs";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Middleware to handle the review page for the service.
|
|
9
|
+
* This middleware processes the review page, populates form data, and shows validation errors.
|
|
10
|
+
*/
|
|
11
|
+
export function govcyReviewPageHandler() {
|
|
12
|
+
return (req, res, next) => {
|
|
13
|
+
try {
|
|
14
|
+
const { siteId } = req.params;
|
|
15
|
+
|
|
16
|
+
// Create a deep copy of the service to avoid modifying the original
|
|
17
|
+
let serviceCopy = req.serviceData;
|
|
18
|
+
|
|
19
|
+
// Deep copy renderer pageData from
|
|
20
|
+
let pageData = JSON.parse(JSON.stringify(govcyResources.staticResources.rendererPageData));
|
|
21
|
+
|
|
22
|
+
// Base page template structure
|
|
23
|
+
let pageTemplate = {
|
|
24
|
+
sections: [
|
|
25
|
+
{
|
|
26
|
+
name: "beforeMain",
|
|
27
|
+
elements: [govcyResources.staticResources.elements.backLink]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
// Construct page title
|
|
32
|
+
const pageH1 = {
|
|
33
|
+
element: "textElement",
|
|
34
|
+
params: {
|
|
35
|
+
type: "h1",
|
|
36
|
+
text: govcyResources.staticResources.text.checkYourAnswersTitle
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Construct submit button
|
|
41
|
+
const submitButton = {
|
|
42
|
+
element: "form",
|
|
43
|
+
params: {
|
|
44
|
+
action: govcyResources.constructPageUrl(siteId, "review"),
|
|
45
|
+
method: "POST",
|
|
46
|
+
elements: [
|
|
47
|
+
{
|
|
48
|
+
element: "button",
|
|
49
|
+
params: {
|
|
50
|
+
type: "submit",
|
|
51
|
+
variant: "success",
|
|
52
|
+
text: govcyResources.staticResources.text.submit
|
|
53
|
+
}
|
|
54
|
+
}, govcyResources.csrfTokenInput(req.csrfToken())
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Generate the summary list using the utility function
|
|
59
|
+
let printFriendlyData = preparePrintFriendlyData(req, siteId, serviceCopy);
|
|
60
|
+
let summaryList = generateReviewSummary(printFriendlyData,req, siteId);
|
|
61
|
+
|
|
62
|
+
//--------- Handle Validation Errors ---------
|
|
63
|
+
// Check if validation errors exist in the session
|
|
64
|
+
const validationErrors = dataLayer.getSiteSubmissionErrors(req.session, siteId);
|
|
65
|
+
let mainElements = [];
|
|
66
|
+
if (validationErrors ) {
|
|
67
|
+
for (const error in validationErrors.errors) {
|
|
68
|
+
validationErrors.errorSummary.push({
|
|
69
|
+
link: govcyResources.constructPageUrl(siteId, validationErrors.errors[error].pageUrl, "review"), //`/${siteId}/${error.pageUrl}`,
|
|
70
|
+
text: validationErrors.errors[error].message
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
mainElements.push(govcyResources.errorSummary(validationErrors.errorSummary));
|
|
74
|
+
}
|
|
75
|
+
//--------- End Handle Validation Errors ---------
|
|
76
|
+
|
|
77
|
+
// Add elements to the main section, the H1, summary list, the submit button and the JS
|
|
78
|
+
mainElements.push(pageH1, summaryList, submitButton, govcyResources.staticResources.elements["govcyFormsJs"]);
|
|
79
|
+
// Append generated summary list to the page template
|
|
80
|
+
pageTemplate.sections.push({ name: "main", elements: mainElements });
|
|
81
|
+
|
|
82
|
+
//if user is logged in add he user bane section in the page template
|
|
83
|
+
if (dataLayer.getUser(req.session)) {
|
|
84
|
+
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//prepare pageData
|
|
88
|
+
pageData.site = serviceCopy.site;
|
|
89
|
+
pageData.pageData.title = govcyResources.staticResources.text.checkYourAnswersTitle;
|
|
90
|
+
|
|
91
|
+
// Attach processed page data to the request
|
|
92
|
+
req.processedPage = {
|
|
93
|
+
pageData: pageData,
|
|
94
|
+
pageTemplate: pageTemplate
|
|
95
|
+
};
|
|
96
|
+
logger.debug("Processed review page data:", req.processedPage, req);
|
|
97
|
+
next();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return next(error); // Pass error to govcyHttpErrorHandler
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
2
|
+
import { validateFormElements } from "../utils/govcyValidator.mjs"; // Import your validator
|
|
3
|
+
import * as dataLayer from "../utils/govcyDataLayer.mjs";
|
|
4
|
+
import { logger } from "../utils/govcyLogger.mjs";
|
|
5
|
+
import {prepareSubmissionData, prepareSubmissionDataAPI, generateSubmitEmail } from "../utils/govcySubmitData.mjs";
|
|
6
|
+
import { govcyApiRequest } from "../utils/govcyApiRequest.mjs";
|
|
7
|
+
import { getEnvVariable } from "../utils/govcyEnvVariables.mjs";
|
|
8
|
+
import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
|
|
9
|
+
import { sendEmail } from "../utils/govcyNotification.mjs"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Middleware to handle review page form submission
|
|
13
|
+
* This middleware processes the review page, validates form data, and shows validation errors.
|
|
14
|
+
*/
|
|
15
|
+
export function govcyReviewPostHandler() {
|
|
16
|
+
return async (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const { siteId } = req.params;
|
|
19
|
+
|
|
20
|
+
// ✅ Load service and check if it exists
|
|
21
|
+
const service = req.serviceData;
|
|
22
|
+
let validationErrors = {};
|
|
23
|
+
|
|
24
|
+
// Loop through all pages in the service
|
|
25
|
+
for (const page of service.pages) {
|
|
26
|
+
//get page url
|
|
27
|
+
const pageUrl = page.pageData.url;
|
|
28
|
+
|
|
29
|
+
// Find the form definition inside `pageTemplate.sections`
|
|
30
|
+
let formElement = null;
|
|
31
|
+
for (const section of page.pageTemplate.sections) {
|
|
32
|
+
formElement = section.elements.find(el => el.element === "form");
|
|
33
|
+
if (formElement) break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!formElement) continue; // Skip pages without forms
|
|
37
|
+
|
|
38
|
+
// Get stored form data for this page (or default to empty)
|
|
39
|
+
const formData = dataLayer.getPageData(req.session, siteId, pageUrl) || {};
|
|
40
|
+
|
|
41
|
+
// Run validations
|
|
42
|
+
const errors = validateFormElements(formElement.params.elements, formData, pageUrl);
|
|
43
|
+
|
|
44
|
+
// Add errors to the validationErrors object
|
|
45
|
+
validationErrors = { ...validationErrors, ...errors };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ❌ Return validation errors if any exist
|
|
49
|
+
if (Object.keys(validationErrors).length > 0) {
|
|
50
|
+
logger.debug("🚨 Validation errors:", validationErrors, req);
|
|
51
|
+
logger.info("🚨 Validation errors:", req.originalUrl);
|
|
52
|
+
dataLayer.storeSiteValidationErrors(req.session, siteId, validationErrors);
|
|
53
|
+
//redirect to the same page with error summary
|
|
54
|
+
return res.redirect(govcyResources.constructErrorSummaryUrl(req.originalUrl));
|
|
55
|
+
} else {
|
|
56
|
+
// ------------ DO SUBMISSION ---------------------
|
|
57
|
+
// get the submission API endpoint URL, clientKey, serviceId from the environment variable (handle edge cases)
|
|
58
|
+
const submissionUrl = getEnvVariable(service?.site?.submissionAPIEndpoint?.url || "", false);
|
|
59
|
+
const clientKey = getEnvVariable(service?.site?.submissionAPIEndpoint?.clientKey || "", false);
|
|
60
|
+
const serviceId = getEnvVariable(service?.site?.submissionAPIEndpoint?.serviceId || "", false);
|
|
61
|
+
if (!submissionUrl) {
|
|
62
|
+
return handleMiddlewareError("🚨 Submission API endpoint URL is missing", 500, next);
|
|
63
|
+
}
|
|
64
|
+
if (!clientKey) {
|
|
65
|
+
return handleMiddlewareError("🚨 Submission API clientKey is missing", 500, next);
|
|
66
|
+
}
|
|
67
|
+
if (!serviceId) {
|
|
68
|
+
return handleMiddlewareError("🚨 Submission API serviceId is missing", 500, next);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Prepare submission data
|
|
72
|
+
const submissionData = prepareSubmissionData(req, siteId, service);
|
|
73
|
+
|
|
74
|
+
// Prepare submission data for API
|
|
75
|
+
const submissionDataAPI = prepareSubmissionDataAPI(submissionData);
|
|
76
|
+
|
|
77
|
+
// Call the API to submit the data
|
|
78
|
+
const response = await govcyApiRequest(
|
|
79
|
+
"post", // Use POST method
|
|
80
|
+
submissionUrl, // Use the submission URL from the environment variable
|
|
81
|
+
submissionDataAPI, // Pass the prepared submission data
|
|
82
|
+
true, // Use access token authentication
|
|
83
|
+
dataLayer.getUser(req.session), // Get the user from the session
|
|
84
|
+
{
|
|
85
|
+
accept: "text/plain", // Set Accept header to text/plain
|
|
86
|
+
"client-key": clientKey, // Set the client key header
|
|
87
|
+
"service-id": serviceId, // Set the service ID header
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Check if the response is successful
|
|
92
|
+
if (response.Succeeded) {
|
|
93
|
+
let referenceNo = response?.Data?.submission_id || "";
|
|
94
|
+
// Add the reference number to the submission data
|
|
95
|
+
submissionData.referenceNumber = referenceNo;
|
|
96
|
+
logger.info("✅ Data submitted", siteId, referenceNo);
|
|
97
|
+
// handle data layer submission
|
|
98
|
+
dataLayer.storeSiteSubmissionData(
|
|
99
|
+
req.session,
|
|
100
|
+
siteId,
|
|
101
|
+
submissionData);
|
|
102
|
+
|
|
103
|
+
//-- Send email to user
|
|
104
|
+
// Generate the email body
|
|
105
|
+
let emailBody = generateSubmitEmail(service, submissionData.print_friendly_data, referenceNo, req);
|
|
106
|
+
logger.debug("Email generated:", emailBody);
|
|
107
|
+
// Send the email
|
|
108
|
+
sendEmail(service.site.title[service.site.lang],emailBody,[dataLayer.getUser(req.session).email], "eMail").catch(err => {
|
|
109
|
+
logger.error("Email sending failed (async):", err);
|
|
110
|
+
});
|
|
111
|
+
// --- End of email sending
|
|
112
|
+
|
|
113
|
+
logger.debug("🔄 Redirecting to success page:", req);
|
|
114
|
+
// redirect to success
|
|
115
|
+
return res.redirect(govcyResources.constructPageUrl(siteId, `success`));
|
|
116
|
+
|
|
117
|
+
// logger.debug("The submission data prepared:", printFriendlyData);
|
|
118
|
+
// let reviewSummary = generateReviewSummary(printFriendlyData,req, siteId, false);
|
|
119
|
+
// res.send(emailBody);
|
|
120
|
+
|
|
121
|
+
// // Clear any existing submission errors from the session
|
|
122
|
+
// dataLayer.clearSiteSubmissionErrors(req.session, siteId);
|
|
123
|
+
} else {
|
|
124
|
+
// Handle submission failure
|
|
125
|
+
const errorCode = response.ErrorCode;
|
|
126
|
+
const errorPage = service.site?.submissionAPIEndpoint?.response?.errorResponse?.[errorCode]?.page;
|
|
127
|
+
|
|
128
|
+
if (errorPage) {
|
|
129
|
+
logger.info("🚨 Submission returned failed:", response.ErrorCode);
|
|
130
|
+
return res.redirect(errorPage);
|
|
131
|
+
} else {
|
|
132
|
+
return handleMiddlewareError("🚨 Unknown error code received from API.", 500, next);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Proceed to final submission if no errors
|
|
139
|
+
// return next();
|
|
140
|
+
// print the submission data to the page
|
|
141
|
+
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return next(error);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { govcyFrontendRenderer } from '@gov-cy/govcy-frontend-renderer';
|
|
2
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
3
|
+
import * as dataLayer from "../utils/govcyDataLayer.mjs";
|
|
4
|
+
import {listAvailableSiteConfigs, getServiceConfigData} from "../utils/govcyLoadConfigData.mjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Middleware function to handle the route page.
|
|
8
|
+
* This function renders the available services page with a list of available sites.
|
|
9
|
+
*/
|
|
10
|
+
export function govcyRoutePageHandler(req, res, next) {
|
|
11
|
+
|
|
12
|
+
// if current service cookie is set redirect to that page
|
|
13
|
+
if (req.cookies.cs) {
|
|
14
|
+
const siteId = req.cookies.cs;
|
|
15
|
+
const serviceData = getServiceConfigData(siteId, req.globalLang);
|
|
16
|
+
if (serviceData.site && serviceData.site.homeRedirectPage) {
|
|
17
|
+
// redirect to the homeRedirectPage cookie
|
|
18
|
+
return res.redirect(serviceData.site.homeRedirectPage);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Deep copy renderer pageData from
|
|
23
|
+
let pageData = JSON.parse(JSON.stringify(govcyResources.staticResources.rendererPageData));
|
|
24
|
+
const listOfAvailableSites = listAvailableSiteConfigs();
|
|
25
|
+
|
|
26
|
+
// Construct the page template
|
|
27
|
+
let pageTemplate = govcyResources.availableServicesPageTemplate(listOfAvailableSites, req.globalLang);
|
|
28
|
+
//use lang from middleware
|
|
29
|
+
pageData.site.lang = req.globalLang;
|
|
30
|
+
//if user is logged in add he user bane section in the page template
|
|
31
|
+
if (dataLayer.getUser(req.session)) {
|
|
32
|
+
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
33
|
+
}
|
|
34
|
+
const renderer = new govcyFrontendRenderer();
|
|
35
|
+
const html = renderer.renderFromJSON(pageTemplate, pageData);
|
|
36
|
+
res.send(html);
|
|
37
|
+
}
|