@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,85 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { logger } from "./govcyLogger.mjs";
|
|
3
|
+
import { getEnvVariable } from "./govcyEnvVariables.mjs";
|
|
4
|
+
import https from 'https';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Utility to send email using the Notifications API
|
|
8
|
+
* @param {string} subject - Email subject
|
|
9
|
+
* @param {string} body - Email body content
|
|
10
|
+
* @param {string[]} recipients - List of recipient email addresses
|
|
11
|
+
* @param {string} [channel='eMail'] - Channel to use (default: 'eMail')
|
|
12
|
+
* @param {Array<{ Name: string, Content: string }>} [attachments=[]] - List of attachments (optional)
|
|
13
|
+
* @param {number} retries - Number of retry attempts (default: 3)
|
|
14
|
+
* @returns {Promise<object>} - API response
|
|
15
|
+
*/
|
|
16
|
+
export async function sendEmail(subject, body, recipients, channel = "eMail", attachments = [], retries = 3) {
|
|
17
|
+
// Test https://dis.dev.gateway.local/DITS.CeGG.DIS.Notifications/
|
|
18
|
+
// Production https://dis.gateway.local/DITS.CeGG.DIS.Notifications/
|
|
19
|
+
//DSF SWAGGER: https://dsf-api-dev.dmrid.gov.cy/index.html
|
|
20
|
+
const url = getEnvVariable("DSF_API_GTW_NOTIFICATION_API_URL");
|
|
21
|
+
let attempt = 0;
|
|
22
|
+
|
|
23
|
+
const payload = {
|
|
24
|
+
subject: subject,
|
|
25
|
+
body: body,
|
|
26
|
+
channel: channel,
|
|
27
|
+
recipients: recipients,
|
|
28
|
+
attachments: attachments,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
while (attempt < retries) {
|
|
32
|
+
try {
|
|
33
|
+
logger.debug(`📤 Sending email (Attempt ${attempt + 1})`, { url, payload });
|
|
34
|
+
|
|
35
|
+
const response = await axios.post(url, payload, {
|
|
36
|
+
timeout: 10000, // 10 seconds timeout
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
"Accept": "text/plain",
|
|
40
|
+
"client-key": `${getEnvVariable("DSF_API_GTW_CLIENT_ID")}`,
|
|
41
|
+
"dsfgtw-api-key" : `${getEnvVariable("DSF_API_GTW_SECRET")}`,
|
|
42
|
+
"service-id": `${getEnvVariable("DSF_API_GTW_SERVICE_ID")}`
|
|
43
|
+
},
|
|
44
|
+
httpsAgent: new https.Agent({ rejectUnauthorized: false })
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Check if the response status is 200
|
|
48
|
+
if (response.status !== 200) {
|
|
49
|
+
throw new Error(`Unexpected HTTP status: ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let responseData = response.data;
|
|
53
|
+
|
|
54
|
+
// Check if the response data contains the expected structure
|
|
55
|
+
const { succeeded, errorCode, errorMessage } = responseData;
|
|
56
|
+
logger.debug(`📥 Received API response data`, { succeeded, errorCode, errorMessage });
|
|
57
|
+
if (typeof succeeded !== "boolean") {
|
|
58
|
+
throw new Error("Invalid API response structure: Succeeded must be a boolean");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!succeeded && typeof errorCode !== "number") {
|
|
62
|
+
throw new Error("Invalid API response structure: ErrorCode must be a number when Succeeded is false");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
logger.info(`✅ Email sent successfully`, responseData);
|
|
66
|
+
return responseData.data; // Return the successful response
|
|
67
|
+
} catch (error) {
|
|
68
|
+
attempt++;
|
|
69
|
+
logger.debug(`🚨 Email sending failed (Attempt ${attempt})`, {
|
|
70
|
+
message: error.message,
|
|
71
|
+
status: error.response?.status,
|
|
72
|
+
data: error.response?.data,
|
|
73
|
+
error: error
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (attempt >= retries) {
|
|
77
|
+
logger.error(`🚨 Email sending failed after ${retries} attempts`, error.message);
|
|
78
|
+
throw new Error(error.responseData?.data?.ErrorMessage || "Email sending failed after retries");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
logger.info(`🔄 Retrying email sending (Attempt ${attempt + 1})...`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a PDF from HTML and returns it as a Buffer.
|
|
5
|
+
* @param {string} html - The full HTML string of the document (should be accessible HTML).
|
|
6
|
+
* @returns {Promise<Buffer>} - The generated PDF buffer.
|
|
7
|
+
*/
|
|
8
|
+
export async function generatePDF(html) {
|
|
9
|
+
const browser = await puppeteer.launch({ headless: 'new' });
|
|
10
|
+
const page = await browser.newPage();
|
|
11
|
+
|
|
12
|
+
await page.setContent(html, { waitUntil: 'networkidle0' });
|
|
13
|
+
|
|
14
|
+
const pdfUint8Array = await page.pdf({
|
|
15
|
+
format: 'A4',
|
|
16
|
+
printBackground: true,
|
|
17
|
+
displayHeaderFooter: false,
|
|
18
|
+
preferCSSPageSize: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await browser.close();
|
|
22
|
+
|
|
23
|
+
// Convert Uint8Array to Buffer
|
|
24
|
+
const pdfBuffer = Buffer.from(pdfUint8Array);
|
|
25
|
+
|
|
26
|
+
return pdfBuffer;
|
|
27
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module govcyReviewSummary
|
|
3
|
+
* @fileoverview This module generates a review summary list based on user input.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
7
|
+
import * as dataLayer from "./govcyDataLayer.mjs";
|
|
8
|
+
import { ALLOWED_FORM_ELEMENTS } from "./govcyConstants.mjs";
|
|
9
|
+
/**
|
|
10
|
+
* Generate a review summary list based on the user's input.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} req the request object
|
|
13
|
+
* @param {string} siteId the site id
|
|
14
|
+
* @param {object} service the service object
|
|
15
|
+
* @returns {object} summaryList the summary list object
|
|
16
|
+
*/
|
|
17
|
+
export function govcyGenerateReviewSummary(req, siteId, service) {
|
|
18
|
+
// Base summary list structure
|
|
19
|
+
let summaryList = { element: "summaryList", params: { items: [] } };
|
|
20
|
+
|
|
21
|
+
// Define allowed form elements
|
|
22
|
+
//TODO: Handle textInput
|
|
23
|
+
const allowedElements = ALLOWED_FORM_ELEMENTS;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Helper function to retrieve user input from session.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} pageUrl the page url
|
|
29
|
+
* @param {string} elementName the element name
|
|
30
|
+
* @returns {string} the session value or an empty string if not found
|
|
31
|
+
*/
|
|
32
|
+
function getSessionValue(pageUrl, elementName) {
|
|
33
|
+
return dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName) || ""; // Use the utility function to get the value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helper function to format the date input.
|
|
38
|
+
* @param {string} pageUrl the page url
|
|
39
|
+
* @param {string} elementName the element name
|
|
40
|
+
* @param {string} lang the language
|
|
41
|
+
* @returns {string} the formatted date input
|
|
42
|
+
*/
|
|
43
|
+
function formatDateInput(pageUrl, elementName, lang) {
|
|
44
|
+
const day = getSessionValue(pageUrl, `${elementName}_day`);
|
|
45
|
+
const month = getSessionValue(pageUrl, `${elementName}_month`);
|
|
46
|
+
const year = getSessionValue(pageUrl, `${elementName}_year`);
|
|
47
|
+
|
|
48
|
+
if (!day || !month || !year) return "";
|
|
49
|
+
|
|
50
|
+
return `${day}/${month}/${year}`; // EU format: DD/MM/YYYY
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Helper function to get the item value of checkboxes based on the selected value.
|
|
55
|
+
* @param {object} formElement the form element
|
|
56
|
+
* @param {sting} value the value
|
|
57
|
+
* @param {string} lang the language
|
|
58
|
+
* @returns {string} the item value of checkboxes
|
|
59
|
+
*/
|
|
60
|
+
function processCheckboxes(formElement, value, lang) {
|
|
61
|
+
let valueString = "";
|
|
62
|
+
// Check if the value is an array and process accordingly
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
value.forEach(selectedValue => {
|
|
65
|
+
const matchedItem = formElement.params.items.find(item => item.value === selectedValue);
|
|
66
|
+
if (matchedItem) {
|
|
67
|
+
valueString += `${matchedItem.text[lang]}, `;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// If the value is a string, find the corresponding item
|
|
71
|
+
} else if (typeof value === "string") {
|
|
72
|
+
const matchedItem = formElement.params.items.find(item => item.value === value);
|
|
73
|
+
if (matchedItem) {
|
|
74
|
+
valueString = matchedItem.text[lang];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Remove trailing comma and space
|
|
78
|
+
return valueString.replace(/,\s*$/, ""); // Remove trailing comma
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Helper function to get the item value of radios or select based on the selected value.
|
|
83
|
+
* @param {object} formElement the form element
|
|
84
|
+
* @param {string} value the value
|
|
85
|
+
* @param {string} lang the language
|
|
86
|
+
* @returns {string} the item value of radios or select
|
|
87
|
+
*/
|
|
88
|
+
function processRadiosOrSelect(formElement, value, lang) {
|
|
89
|
+
if (typeof value === "string") {
|
|
90
|
+
const matchedItem = formElement.params.items.find(item => item.value === value);
|
|
91
|
+
if (matchedItem) {
|
|
92
|
+
return matchedItem.text[lang];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Helper function to create a summary list item.
|
|
100
|
+
* @param {object} formElement the form element
|
|
101
|
+
* @param {string} value the value
|
|
102
|
+
* @param {string} lang the language
|
|
103
|
+
* @returns {object} the summary list item
|
|
104
|
+
*/
|
|
105
|
+
function createSummaryListItem(formElement, value, lang) {
|
|
106
|
+
return {
|
|
107
|
+
"key": formElement.params.label || formElement.params.legend || { "en": formElement.params.name, "el": formElement.params.name, "tr": formElement.params.name },
|
|
108
|
+
"value": [
|
|
109
|
+
{
|
|
110
|
+
"element": "textElement",
|
|
111
|
+
"params": {
|
|
112
|
+
"text": { "en": value, "el": value, "tr": value},
|
|
113
|
+
"type": "span"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Process each page in the service
|
|
121
|
+
service.pages.forEach(page => {
|
|
122
|
+
page.pageTemplate.sections.forEach(section => {
|
|
123
|
+
if (!section.elements) return;
|
|
124
|
+
|
|
125
|
+
section.elements.forEach(element => {
|
|
126
|
+
if (element.element === "form") {
|
|
127
|
+
let summaryListInner = { element: "summaryList", params: { items: [] } };
|
|
128
|
+
|
|
129
|
+
element.params.elements.forEach(formElement => {
|
|
130
|
+
if (allowedElements.includes(formElement.element)) {
|
|
131
|
+
let value = getSessionValue(page.pageData.url, formElement.params.name);
|
|
132
|
+
|
|
133
|
+
// Handle checkboxes
|
|
134
|
+
if (formElement.element === "checkboxes" && value.length > 0) {
|
|
135
|
+
value = processCheckboxes(formElement, value, req.globalLang);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Handle select and radios
|
|
139
|
+
if ((formElement.element === "select" || formElement.element === "radios") && typeof value === "string") {
|
|
140
|
+
value = processRadiosOrSelect(formElement, value, req.globalLang);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Handle date input
|
|
144
|
+
if (formElement.element === "dateInput") {
|
|
145
|
+
value = formatDateInput(page.pageData.url, formElement.params.name, req.globalLang);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Add to summary list
|
|
149
|
+
summaryListInner.params.items.push(createSummaryListItem(formElement, value, req.globalLang));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Handle conditional elements inside radios
|
|
153
|
+
if (formElement.element === "radios") {
|
|
154
|
+
let selectedRadioValue = getSessionValue(page.pageData.url, formElement.params.name);
|
|
155
|
+
|
|
156
|
+
formElement.params.items.forEach(item => {
|
|
157
|
+
if (item.conditionalElements && selectedRadioValue === item.value) { // ✅ New: Only process matching radio option
|
|
158
|
+
item.conditionalElements.forEach(conditionalElement => {
|
|
159
|
+
if (allowedElements.includes(conditionalElement.element)) {
|
|
160
|
+
let value = getSessionValue(page.pageData.url, conditionalElement.params.name);
|
|
161
|
+
|
|
162
|
+
// Handle checkboxes
|
|
163
|
+
if (conditionalElement.element === "checkboxes" && value.length > 0) {
|
|
164
|
+
value = processCheckboxes(conditionalElement, value, req.globalLang);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Handle select and radios
|
|
168
|
+
if ((conditionalElement.element === "select" || conditionalElement.element === "radios") && typeof value === "string") {
|
|
169
|
+
value = processRadiosOrSelect(conditionalElement, value, req.globalLang);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Handle date input
|
|
173
|
+
if (conditionalElement.element === "dateInput") {
|
|
174
|
+
value = formatDateInput(page.pageData.url, conditionalElement.params.name, req.globalLang);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add to summary list
|
|
178
|
+
summaryListInner.params.items.push(createSummaryListItem(conditionalElement, value, req.globalLang));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Add inner summary list to the main summary list
|
|
187
|
+
summaryList.params.items.push({
|
|
188
|
+
"key": page.pageData.title,
|
|
189
|
+
"value": [summaryListInner],
|
|
190
|
+
"actions": [ //add change link
|
|
191
|
+
{
|
|
192
|
+
text:govcyResources.staticResources.text.change,
|
|
193
|
+
classes: govcyResources.staticResources.other.noPrintClass,
|
|
194
|
+
href: govcyResources.constructPageUrl(siteId, page.pageData.url, "review"),
|
|
195
|
+
visuallyHiddenText: page.pageData.title
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return summaryList;
|
|
205
|
+
}
|