@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,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,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
|
+
});
|