@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,101 @@
1
+ import { govcyApiRequest } from "../utils/govcyApiRequest.mjs";
2
+ import { logger } from "../utils/govcyLogger.mjs";
3
+ import { getEnvVariable } from "../utils/govcyEnvVariables.mjs";
4
+ import { getPageConfigData } from "../utils/govcyLoadConfigData.mjs";
5
+ import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
6
+ import * as dataLayer from "../utils/govcyDataLayer.mjs";
7
+
8
+ // Helper to show error page or redirect (reuse your review handler logic)
9
+ function handleEligibilityError(res, errorPage,next) {
10
+ if (errorPage) {
11
+ return res.redirect(errorPage);
12
+ }
13
+ // fallback: show generic error
14
+ return handleMiddlewareError("Eligibility check failed", 403, next);
15
+ }
16
+
17
+ export function govcyServiceEligibilityHandler(checkForForm = false) {
18
+ return async (req, res, next) => {
19
+ try {
20
+ const service = req.serviceData;
21
+ // Extract siteId and pageUrl from request
22
+ let { siteId, pageUrl } = req.params;
23
+ // If `checkForForm` is set, check if page has no form and skip the eligibility check
24
+ if (checkForForm) {
25
+ // 🏠 Handle index page: If pageUrl is undefined (meaning the user accessed `/:siteId`), set a default value or handle accordingly
26
+ if (!pageUrl) pageUrl = "index";
27
+ // 🔍 Find the page by pageUrl
28
+ const page = getPageConfigData(service, pageUrl);
29
+ if (!page || !page.pageTemplate) return next(); // Defensive: skip if no template
30
+ // Deep copy pageTemplate to avoid modifying the original
31
+ const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
32
+
33
+ // Check if any section contains a form element
34
+ const hasForm = pageTemplateCopy.sections?.some(section =>
35
+ section.elements?.some(el => el.element === "form")
36
+ );
37
+ if (!hasForm) {
38
+ // No form found, skip eligibility check
39
+ return next();
40
+ }
41
+ // else: continue with eligibility check
42
+ }
43
+ const eligibilityEndpoints = service?.site?.eligibilityAPIEndpoints || [];
44
+ const user = dataLayer.getUser(req.session); // Get the user from the session;
45
+
46
+ for (const endpoint of eligibilityEndpoints) {
47
+ // get the API endpoint URL, clientKey, serviceId from the environment variable (handle edge cases)
48
+ const url = getEnvVariable(endpoint?.url || "", false);
49
+ const clientKey = getEnvVariable(endpoint?.clientKey || "", false);
50
+ const serviceId = getEnvVariable(endpoint?.serviceId || "", false);
51
+ const cashingTimeoutMinutes = endpoint?.cashingTimeoutMinutes || 0; // Default to 0 if not set
52
+ if (!url) {
53
+ return handleMiddlewareError("🚨 Service eligibility API endpoint URL is missing", 500, next);
54
+ }
55
+ if (!clientKey) {
56
+ return handleMiddlewareError("🚨 Service eligibility API clientKey is missing", 500, next);
57
+ }
58
+ if (!serviceId) {
59
+ return handleMiddlewareError("🚨 Service eligibility API serviceId is missing", 500, next);
60
+ }
61
+
62
+ const endpointKey = endpoint?.url || "defaultEndpoint";
63
+ const maxAgeMs = cashingTimeoutMinutes * 60 * 1000; // convert minutes to milliseconds
64
+ const params = endpoint?.params || {};
65
+ const method = (endpoint?.method || "GET").toLowerCase();
66
+ // Check if eligibility result is cached
67
+ let response = dataLayer.getSiteEligibilityResult(req.session, siteId, endpointKey, maxAgeMs);
68
+ if (!response) {
69
+ // Call the eligibility API
70
+ response = await govcyApiRequest(
71
+ method,
72
+ url,
73
+ params,
74
+ true,
75
+ user,
76
+ {
77
+ accept: "text/plain", // Set Accept header to text/plain
78
+ "client-key": clientKey, // Set the client key header
79
+ "service-id": serviceId, // Set the service ID header
80
+ }
81
+ );
82
+ // Cache the result
83
+ dataLayer.storeSiteEligibilityResult(req.session, siteId, endpointKey, response);
84
+ }
85
+
86
+ // If not eligible, handle error
87
+ if (!response.Succeeded) {
88
+ // Try to find a custom error page for this error code
89
+ const errorPage = endpoint.response?.errorResponse?.[String(response.ErrorCode)]?.page;
90
+ logger.info(`Eligibility check failed: ${response.ErrorMessage || response.ErrorCode}`);
91
+ return handleEligibilityError(res, errorPage, next);
92
+ }
93
+ }
94
+
95
+ // All checks passed
96
+ next();
97
+ } catch (err) {
98
+ return next(err); // Pass error to govcyHttpErrorHandler
99
+ }
100
+ };
101
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Middleware to ensure session storage exists for GovCy data.
3
+ */
4
+ export function govcySessionData(req, res, next) {
5
+ if (!req.session.siteData) {
6
+ req.session.siteData = {}; // Ensure session storage exists
7
+ }
8
+ next();
9
+ }
@@ -0,0 +1,112 @@
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 { handleMiddlewareError } from "../utils/govcyUtils.mjs";
5
+ import { generateReviewSummary } from "../utils/govcySubmitData.mjs";
6
+
7
+
8
+ /**
9
+ * Middleware to handle the success page for the service.
10
+ * This middleware shows the success page.
11
+ * @param {boolean} isPDF - True if the success page is for a PDF, false otherwise
12
+ *
13
+ */
14
+ export function govcySuccessPageHandler(isPDF = false) {
15
+ return (req, res, next) => {
16
+ try {
17
+ const { siteId } = req.params;
18
+
19
+ // Create a deep copy of the service to avoid modifying the original
20
+ let serviceCopy = req.serviceData;
21
+
22
+ // Get the submission data
23
+ let submissionData = dataLayer.getSiteSubmissionData(req.session, siteId);
24
+ // ❌ Check if submission data is empty
25
+ if (!submissionData || Object.keys(submissionData).length === 0) {
26
+ return handleMiddlewareError("🚨 Submission data not found.", 404, next);
27
+ }
28
+ // Deep copy renderer pageData from
29
+ let pageData = JSON.parse(JSON.stringify(govcyResources.staticResources.rendererPageData));
30
+
31
+ if (isPDF) {
32
+ pageData.pageData.mainLayout = "max-width";
33
+ }
34
+ // Base page template structure
35
+ let pageTemplate = {
36
+ sections: [
37
+ // {
38
+ // name: "beforeMain",
39
+ // elements: [govcyResources.staticResources.elements.backLink]
40
+ // }
41
+ ]
42
+ };
43
+ // Construct page title
44
+ const weHaveSendYouAnEmail = {
45
+ element: "textElement",
46
+ params: {
47
+ type: "p",
48
+ text: govcyResources.staticResources.text.weHaveSendYouAnEmail
49
+ }
50
+ };
51
+
52
+ // Construct page title
53
+ const theDataFromYourRequest = {
54
+ element: "textElement",
55
+ params: {
56
+ type: "p",
57
+ text: govcyResources.staticResources.text.theDataFromYourRequest
58
+ }
59
+ };
60
+ // Construct page title
61
+ const successPanel = {
62
+ element: "panel",
63
+ params: {
64
+ header: govcyResources.staticResources.text.submissionSuccessTitle,
65
+ body: govcyResources.staticResources.text.yourSubmissionId,
66
+ referenceNumber: govcyResources.getSameMultilingualObject(serviceCopy.site.languages,submissionData.referenceNumber)
67
+ }
68
+ };
69
+
70
+ const pdfLink = {
71
+ element: "htmlElement",
72
+ params: {
73
+ text: govcyResources.getSubmissionPDFLinkHtml(siteId)
74
+ }
75
+ }
76
+
77
+ let summaryList = submissionData.renderer_data;
78
+
79
+ let mainElements = [];
80
+ // Add elements to the main section
81
+ mainElements.push(
82
+ successPanel,
83
+ weHaveSendYouAnEmail,
84
+ pdfLink,
85
+ theDataFromYourRequest,
86
+ summaryList,
87
+ govcyResources.staticResources.elements["govcyFormsJs"]
88
+ );
89
+ // Append generated summary list to the page template
90
+ pageTemplate.sections.push({ name: "main", elements: mainElements });
91
+
92
+ //if user is logged in add he user bane section in the page template
93
+ if (dataLayer.getUser(req.session)) {
94
+ pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
95
+ }
96
+
97
+ //prepare pageData
98
+ pageData.site = serviceCopy.site;
99
+ pageData.pageData.title = govcyResources.staticResources.text.submissionSuccessTitle;
100
+
101
+ // Attach processed page data to the request
102
+ req.processedPage = {
103
+ pageData: pageData,
104
+ pageTemplate: pageTemplate
105
+ };
106
+ logger.debug("Processed success page data:", req.processedPage, req);
107
+ next();
108
+ } catch (error) {
109
+ return next(error); // Pass error to govcyHttpErrorHandler
110
+ }
111
+ };
112
+ }
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 595.276 841.89" enable-background="new 0 0 595.276 841.89" xml:space="preserve">
5
+ <g id="Layer_1">
6
+ <g>
7
+ <g>
8
+ <path fill="#D6D9DC" d="M565.276,30v781.89H30V30H565.276 M595.276,0H0v841.89h595.276V0L595.276,0z"/>
9
+ </g>
10
+ </g>
11
+ <rect x="187.638" y="128.049" fill="#D6D9DC" width="220" height="80"/>
12
+ <rect x="157.638" y="299.945" fill="#B2B4B6" width="280" height="35"/>
13
+ <rect x="182.638" y="355.445" fill="#B2B4B6" width="230" height="35"/>
14
+ <rect x="147.638" y="650.223" fill="#D6D9DC" width="300" height="100"/>
15
+ </g>
16
+ <g id="Layer_2">
17
+ <g>
18
+ </g>
19
+ <g>
20
+ </g>
21
+ <g>
22
+ </g>
23
+ <g>
24
+ </g>
25
+ <g>
26
+ </g>
27
+ <g>
28
+ </g>
29
+ </g>
30
+ </svg>
@@ -0,0 +1,21 @@
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ // --- Show conditionals for checked radios ---
3
+ document.querySelectorAll('.govcy-radio-input[data-aria-controls]:checked').forEach(radio => {
4
+ const targetId = radio.getAttribute('data-aria-controls');
5
+ const targetElement = document.getElementById(targetId);
6
+
7
+ if (targetElement) {
8
+ targetElement.classList.remove('govcy-radio__conditional--hidden');
9
+ }
10
+ });
11
+ // --- Disable submit button after form submission ---
12
+ document.querySelectorAll('form').forEach(form => {
13
+ form.addEventListener('submit', function (e) {
14
+ const submitButton = form.querySelector('[type="submit"]');
15
+ if (submitButton) {
16
+ submitButton.disabled = true;
17
+ submitButton.setAttribute('aria-disabled', 'true');
18
+ }
19
+ });
20
+ });
21
+ });