@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1157 -0
  3. package/package.json +72 -0
  4. package/src/auth/cyLoginAuth.mjs +123 -0
  5. package/src/index.mjs +188 -0
  6. package/src/middleware/cyLoginAuth.mjs +131 -0
  7. package/src/middleware/govcyConfigSiteData.mjs +38 -0
  8. package/src/middleware/govcyCsrf.mjs +36 -0
  9. package/src/middleware/govcyFormsPostHandler.mjs +83 -0
  10. package/src/middleware/govcyHeadersControl.mjs +22 -0
  11. package/src/middleware/govcyHttpErrorHandler.mjs +63 -0
  12. package/src/middleware/govcyLanguageMiddleware.mjs +19 -0
  13. package/src/middleware/govcyLogger.mjs +15 -0
  14. package/src/middleware/govcyManifestHandler.mjs +46 -0
  15. package/src/middleware/govcyPDFRender.mjs +30 -0
  16. package/src/middleware/govcyPageHandler.mjs +110 -0
  17. package/src/middleware/govcyPageRender.mjs +14 -0
  18. package/src/middleware/govcyRequestTimer.mjs +29 -0
  19. package/src/middleware/govcyReviewPageHandler.mjs +102 -0
  20. package/src/middleware/govcyReviewPostHandler.mjs +147 -0
  21. package/src/middleware/govcyRoutePageHandler.mjs +37 -0
  22. package/src/middleware/govcyServiceEligibilityHandler.mjs +101 -0
  23. package/src/middleware/govcySessionData.mjs +9 -0
  24. package/src/middleware/govcySuccessPageHandler.mjs +112 -0
  25. package/src/public/img/Certificate_A4.svg +30 -0
  26. package/src/public/js/govcyForms.js +21 -0
  27. package/src/resources/govcyResources.mjs +430 -0
  28. package/src/standalone.mjs +7 -0
  29. package/src/utils/govcyApiRequest.mjs +114 -0
  30. package/src/utils/govcyConstants.mjs +4 -0
  31. package/src/utils/govcyDataLayer.mjs +311 -0
  32. package/src/utils/govcyEnvVariables.mjs +45 -0
  33. package/src/utils/govcyFormHandling.mjs +148 -0
  34. package/src/utils/govcyLoadConfigData.mjs +135 -0
  35. package/src/utils/govcyLogger.mjs +30 -0
  36. package/src/utils/govcyNotification.mjs +85 -0
  37. package/src/utils/govcyPdfMaker.mjs +27 -0
  38. package/src/utils/govcyReviewSummary.mjs +205 -0
  39. package/src/utils/govcySubmitData.mjs +530 -0
  40. package/src/utils/govcyUtils.mjs +13 -0
  41. 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
+ }