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