@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,311 @@
1
+ /**
2
+ * @module govcyDataLayer
3
+ * @fileoverview This utility provides functions for storing and retrieving data from the session store.
4
+ * It includes functions to initialize the data layer, store page validation errors, store form data,
5
+ * retrieve validation errors, and clear site data.
6
+ *
7
+ */
8
+
9
+ /**
10
+ * Initialize the data layer
11
+ *
12
+ * @param {object} store The session store
13
+ * @param {string} siteId The site id
14
+ * @param {string} pageUrl The page url
15
+ */
16
+ export function initializeSiteData(store, siteId, pageUrl = null) {
17
+ if (!store.siteData) store.siteData = {};
18
+ if (!store.siteData[siteId]) store.siteData[siteId] = {};
19
+ if (!store.siteData[siteId].inputData) store.siteData[siteId].inputData = {};
20
+ if (!store.siteData[siteId].submissionData) store.siteData[siteId].submissionData = {};
21
+
22
+ if (pageUrl && !store.siteData[siteId].inputData[pageUrl]) {
23
+ store.siteData[siteId].inputData[pageUrl] = { formData: {} };
24
+ }
25
+ }
26
+
27
+ // export function getSubmissionData(store, siteId, pageUrl) {
28
+ // return store.siteData?.[siteId]?.inputData?.[pageUrl]?.formData || {};
29
+ // }
30
+
31
+ /**
32
+ * Store the page errors in the data layer
33
+ *
34
+ * The following is an example of the data that will be stored:
35
+ ```json
36
+ {
37
+ "errors": {
38
+ "Iban": {
39
+ "id": "Iban",
40
+ "message": {
41
+ "en": "Enter your IBAN",
42
+ "el": "Εισαγάγετε το IBAN σας"
43
+ },
44
+ "pageUrl": ""
45
+ },
46
+ "Swift": {
47
+ "id": "Swift",
48
+ "message": {
49
+ "en": "Enter your SWIFT",
50
+ "el": "Εισαγάγετε το SWIFT σας"
51
+ },
52
+ "pageUrl": ""
53
+ }
54
+ }
55
+ ```
56
+ * @param {object} store The session store
57
+ * @param {string} siteId The site id
58
+ * @param {string} pageUrl The page url
59
+ * @param {object} validationErrors The validation errors
60
+ * @param {object} formData The form data that produced the errors
61
+ */
62
+ export function storePageValidationErrors(store, siteId, pageUrl, validationErrors, formData) {
63
+ // Ensure session structure is initialized
64
+ initializeSiteData(store, siteId, pageUrl);
65
+
66
+ // Store the validation errors
67
+ store.siteData[siteId].inputData[pageUrl]["validationErrors"] = {
68
+ errors: validationErrors,
69
+ formData: formData,
70
+ errorSummary: []
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Stores the page's form data in the data layer
76
+ *
77
+ * The following is an example of the data that will be stored:
78
+ ```json
79
+ {
80
+ field1: [
81
+ "value1",
82
+ "value2"
83
+ ],
84
+ field2: "value2",
85
+ _csrf: "1234567890"
86
+ }
87
+ ```
88
+ *
89
+ * @param {object} store The session store
90
+ * @param {string} siteId The site id
91
+ * @param {string} pageUrl The page url
92
+ * @param {object} formData The form data to be stored
93
+ */
94
+ export function storePageData(store, siteId, pageUrl, formData) {
95
+ // Ensure session structure is initialized
96
+ initializeSiteData(store, siteId, pageUrl);
97
+
98
+ store.siteData[siteId].inputData[pageUrl]["formData"] = formData;
99
+ }
100
+
101
+ /**
102
+ * Stores the site validation errors in the data layer
103
+ *
104
+ * The following is an example of the data that will be stored:
105
+ *
106
+ ```json
107
+ {
108
+ "errors": {
109
+ "bank-detailsIban": {
110
+ "id": "Iban",
111
+ "message": {
112
+ "en": "Enter your IBAN",
113
+ "el": "Εισαγάγετε το IBAN σας"
114
+ },
115
+ "pageUrl": "bank-details"
116
+ },
117
+ "bank-detailsSwift": {
118
+ "id": "Swift",
119
+ "message": {
120
+ "en": "Enter your SWIFT",
121
+ "el": "Εισαγάγετε το SWIFT σας"
122
+ },
123
+ "pageUrl": "bank-details"
124
+ }
125
+ },
126
+ "errorSummary": []
127
+ }
128
+ ```
129
+ * @param {object} store The session store
130
+ * @param {string} siteId The site id
131
+ * @param {object} validationErrors The validation errors to be stored
132
+ */
133
+ export function storeSiteValidationErrors(store, siteId, validationErrors) {
134
+ initializeSiteData(store, siteId); // Ensure the structure exists
135
+
136
+ store.siteData[siteId]["submissionErrors"] = {
137
+ errors: validationErrors,
138
+ errorSummary: []
139
+ };
140
+ }
141
+
142
+
143
+ /**
144
+ * Stores the submitted site's input data in the data layer and clears the input data
145
+ *
146
+ * Check NOTES.md for sample of the data
147
+ *
148
+ * @param {object} store The session store
149
+ * @param {string} siteId The site id
150
+ * @param {object} submissionData The submission data to be stored
151
+ */
152
+ export function storeSiteSubmissionData(store, siteId, submissionData) {
153
+ initializeSiteData(store, siteId); // Ensure the structure exists
154
+
155
+ // let rawData = getSiteInputData(store, siteId);
156
+ // Store the submission data
157
+ store.siteData[siteId].submissionData = submissionData;
158
+
159
+ // Clear validation errors from the session
160
+ store.siteData[siteId].inputData = {};
161
+ }
162
+
163
+
164
+ /**
165
+ * Store eligibility result for a site and endpoint
166
+ * @param {object} store - session store
167
+ * @param {string} siteId
168
+ * @param {string} endpointKey - unique key for the eligibility endpoint (e.g. resolved URL)
169
+ * @param {object} result - API response
170
+ */
171
+ export function storeSiteEligibilityResult(store, siteId, endpointKey, result) {
172
+
173
+ initializeSiteData(store, siteId); // Ensure the structure exists
174
+
175
+ if (!store.siteData[siteId].eligibility) store.siteData[siteId].eligibility = {};
176
+ store.siteData[siteId].eligibility[endpointKey] = {
177
+ result,
178
+ timestamp: Date.now()
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Get eligibility result for a site and endpoint
184
+ * @param {object} store - session store
185
+ * @param {string} siteId
186
+ * @param {string} endpointKey
187
+ * @param {number} maxAgeMs - max age in ms (optional)
188
+ * @returns {object|null}
189
+ */
190
+ export function getSiteEligibilityResult(store, siteId, endpointKey, maxAgeMs = null) {
191
+ const entry = store?.siteData?.[siteId]?.eligibility?.[endpointKey];
192
+ if (!entry) return null;
193
+ if (maxAgeMs === 0) return null; // 0 Never caches
194
+ if (maxAgeMs && Date.now() - entry.timestamp > maxAgeMs) return null; // Expired
195
+ return entry.result;
196
+ }
197
+
198
+ /**
199
+ * Get the page validation errors from the store and clear them
200
+ *
201
+ * @param {object} store The session store
202
+ * @param {string} siteId The site id
203
+ * @param {string} pageUrl The page url
204
+ * @returns The validation errors for the page or null if none exist. Also clears the errors from the store.
205
+ */
206
+ export function getPageValidationErrors(store, siteId, pageUrl) {
207
+ const validationErrors = store?.siteData?.[siteId]?.inputData?.[pageUrl]?.validationErrors || null;
208
+
209
+ if (validationErrors) {
210
+ // Clear validation errors from the session
211
+ delete store.siteData[siteId].inputData[pageUrl].validationErrors;
212
+ return validationErrors;
213
+ }
214
+
215
+ return null;
216
+ }
217
+
218
+ /**
219
+ * Get the posted page data from the store
220
+ *
221
+ * @param {object} store The session store
222
+ * @param {string} siteId The site id
223
+ * @param {string} pageUrl The page url
224
+ * @returns The form data for the page or an empty object if none exist.
225
+ */
226
+ export function getPageData(store, siteId, pageUrl) {
227
+ return store?.siteData?.[siteId]?.inputData?.[pageUrl]?.formData || {};
228
+ }
229
+
230
+ /**
231
+ * Get the site validation errors from the store and clear them
232
+ *
233
+ * @param {object} store The session store
234
+ * @param {string} siteId |The site id
235
+ * @returns The validation errors for the site or null if none exist. Also clears the errors from the store.
236
+ */
237
+ export function getSiteSubmissionErrors(store, siteId) {
238
+ const validationErrors = store?.siteData?.[siteId]?.submissionErrors || null;
239
+
240
+ if (validationErrors) {
241
+ // Clear validation errors from the session
242
+ delete store.siteData[siteId].submissionErrors;
243
+ return validationErrors;
244
+ }
245
+
246
+ return null;
247
+ }
248
+
249
+ /**
250
+ * Get the site's input data from the store
251
+ *
252
+ * @param {object} store The session store
253
+ * @param {string} siteId |The site id
254
+ * @returns The site input data or null if none exist.
255
+ */
256
+ export function getSiteInputData(store, siteId) {
257
+ const inputData = store?.siteData?.[siteId]?.inputData || {};
258
+
259
+ if (inputData) {
260
+ return inputData;
261
+ }
262
+
263
+ return null;
264
+ }
265
+
266
+ /**
267
+ * Get the site's input data from the store
268
+ *
269
+ * @param {object} store The session store
270
+ * @param {string} siteId |The site id
271
+ * @returns The site input data or null if none exist.
272
+ */
273
+ export function getSiteSubmissionData(store, siteId) {
274
+ initializeSiteData(store, siteId); // Ensure the structure exists
275
+
276
+ const submission = store?.siteData?.[siteId]?.submissionData || {};
277
+
278
+ if (submission) {
279
+ return submission;
280
+ }
281
+
282
+ return null;
283
+ }
284
+
285
+ /**
286
+ * Get the value of a specific form data element from the store
287
+ *
288
+ * @param {object} store The session store
289
+ * @param {string} siteId The site id
290
+ * @param {string} pageUrl The page url
291
+ * @param {string} elementName The element name
292
+ * @returns The value of the form data for the element or an empty string if none exist.
293
+ */
294
+ export function getFormDataValue(store, siteId, pageUrl, elementName) {
295
+ return store?.siteData?.[siteId]?.inputData?.[pageUrl]?.formData?.[elementName] || "";
296
+ }
297
+
298
+ /**
299
+ * Get the user object from the session store
300
+ *
301
+ * @param {object} store The session store
302
+ * @returns The user object from the store or null if it doesn't exist.
303
+ */
304
+ export function getUser(store){
305
+ return store.user || null;
306
+ }
307
+
308
+ export function clearSiteData(store, siteId) {
309
+ delete store?.siteData[siteId];
310
+ }
311
+
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @module govcyEnvVariables
3
+ * @fileoverview This module manages environment variables and settings for the application.
4
+ * It loads environment variables from a .env file in non-production environments,
5
+ * and provides functions to check the current environment and retrieve environment variables.
6
+ */
7
+
8
+ import * as dotenv from 'dotenv';
9
+
10
+ // Load environment variables from .env file only in non-production environments
11
+ if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'staging') {
12
+ dotenv.config();
13
+ }
14
+
15
+ /**
16
+ * Check if the current environment is production or staging
17
+ *
18
+ * @returns {boolean} true if the environment is production or staging, false otherwise
19
+ */
20
+ export function isProdOrStaging() {
21
+ // Determine environment settings
22
+ const ENV = whatsIsMyEnvironment();
23
+ return ENV === 'production' || ENV === 'staging';
24
+ }
25
+
26
+ /**
27
+ * Get the value of an environment variable
28
+ *
29
+ * @param {string} key The environment variable key
30
+ * @param {string} defaultValue The default value to return if the key is not found
31
+ * @returns The value of the environment variable or the default value
32
+ */
33
+ export function getEnvVariable(key, defaultValue = undefined) {
34
+ return process.env[key] || defaultValue;
35
+ }
36
+
37
+ /**
38
+ * Return the current environment (development, staging, production)
39
+ *
40
+ * @returns {string} The current environment (development, staging, production)
41
+ */
42
+ export function whatsIsMyEnvironment() {
43
+ // Determine environment
44
+ return process.env.NODE_ENV || 'development';
45
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @module govcyFormHandling
3
+ * @fileoverview This module provides utility functions for handling form data in a web application.
4
+ * It includes functions to populate form data with session data, handle conditional elements,
5
+ * and show error summary when there are validation errors.
6
+ */
7
+ import { ALLOWED_FORM_ELEMENTS } from "./govcyConstants.mjs";
8
+
9
+
10
+ /**
11
+ * Helper function to populate form data with session data
12
+ * @param {Array} formElements The form elements
13
+ * @param {*} theData The data either from session or request
14
+ */
15
+ export function populateFormData(formElements, theData, validationErrors) {
16
+ const inputElements = ALLOWED_FORM_ELEMENTS;
17
+
18
+ // Recursively populate form data with session data
19
+ formElements.forEach(element => {
20
+ if (inputElements.includes(element.element)) {
21
+ const fieldName = element.params.name;
22
+
23
+ // Handle `dateInput` separately
24
+ if (element.element === "dateInput") {
25
+ element.params.dayValue = theData[`${fieldName}_day`] || "";
26
+ element.params.monthValue = theData[`${fieldName}_month`] || "";
27
+ element.params.yearValue = theData[`${fieldName}_year`] || "";
28
+ //Handle `datePicker` separately
29
+ } else if (element.element === "datePicker") {
30
+ const val = theData[fieldName];
31
+
32
+ // Check if the value exists and matches the D/M/YYYY or DD/MM/YYYY pattern
33
+ if (val && /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(val)) {
34
+ const [day, month, year] = val.split("/").map(Number); // Convert parts to numbers
35
+ const date = new Date(year, month - 1, day); // Month is zero-based in JS
36
+
37
+ // Check if the date is valid (e.g., not 31/02/2020)
38
+ if (
39
+ date.getFullYear() === year &&
40
+ date.getMonth() === month - 1 &&
41
+ date.getDate() === day
42
+ ) {
43
+ // Format it as YYYY-MM-DD for the <input type="date"> value
44
+ const yyyy = year;
45
+ const mm = String(month).padStart(2, '0');
46
+ const dd = String(day).padStart(2, '0');
47
+ element.params.value = `${yyyy}-${mm}-${dd}`;
48
+ } else {
49
+ // Invalid date (e.g., 31/02/2020)
50
+ element.params.value = "";
51
+ }
52
+ } else {
53
+ // Invalid format (not matching D/M/YYYY or DD/MM/YYYY)
54
+ element.params.value = "";
55
+ }
56
+ // Handle all other input elements (textInput, checkboxes, radios, etc.)
57
+ } else {
58
+ element.params.value = theData[fieldName] || "";
59
+ }
60
+
61
+ // if there are validation errors, populate the error message
62
+ if (validationErrors?.errors?.[fieldName]) {
63
+ element.params.error = validationErrors.errors[fieldName].message;
64
+ //populate the error summary
65
+ const elementId = (element.element === "checkboxes" || element.element === "radios") // if checkboxes or radios
66
+ ? `${element.params.id}-option-1` // use the id of the first option
67
+ : (element.element === "dateInput") //if dateInput
68
+ ? `${element.params.id}_day` // use the id of the day input
69
+ : element.params.id; // else use the id of the element
70
+ validationErrors.errorSummary.push({
71
+ link: `#${elementId}`,
72
+ text: validationErrors.errors[fieldName].message
73
+ });
74
+ }
75
+ }
76
+
77
+ // Handle conditional elements inside radios
78
+ if (element.element === "radios" && element.params.items) {
79
+ element.params.items.forEach(item => {
80
+ if (item.conditionalElements) {
81
+ populateFormData(item.conditionalElements, theData, validationErrors);
82
+
83
+ // Check if any conditional element has an error and add to the parent "conditionalHasErrors": true
84
+ if (item.conditionalElements.some(condEl => condEl.params?.error)) {
85
+ item.conditionalHasErrors = true;
86
+ }
87
+ }
88
+ });
89
+ }
90
+ });
91
+ }
92
+
93
+
94
+ /**
95
+ * Filters form data based on the form definition, including conditionals.
96
+ *
97
+ * @param {Array} elements - The form elements (including conditional ones).
98
+ * @param {Object} formData - The submitted form data.
99
+ * @returns {Object} filteredData - The filtered form data.
100
+ */
101
+ export function getFormData(elements, formData) {
102
+ const filteredData = {};
103
+ elements.forEach(element => {
104
+ const { name } = element.params || {};
105
+
106
+ // Check if the element is allowed and has a name
107
+ if (ALLOWED_FORM_ELEMENTS.includes(element.element) && name) {
108
+ // Handle conditional elements (e.g., checkboxes, radios, select)
109
+ if (["checkboxes", "radios", "select"].includes(element.element)) {
110
+ const value = formData[name];
111
+ filteredData[name] = value !== undefined && value !== null ? value : "";
112
+ // Process conditional elements inside items
113
+ if (element.element === "radios" && element.params.items) {
114
+ element.params.items.forEach(item => {
115
+ if (item.conditionalElements) {
116
+ Object.assign(
117
+ filteredData,
118
+ getFormData(item.conditionalElements, formData)
119
+ );
120
+ }
121
+ });
122
+ }
123
+
124
+ }
125
+ // Handle dateInput
126
+ else if (element.element === "dateInput") {
127
+ const day = formData[`${name}_day`];
128
+ const month = formData[`${name}_month`];
129
+ const year = formData[`${name}_year`];
130
+ filteredData[`${name}_day`] = day !== undefined && day !== null ? day : "";
131
+ filteredData[`${name}_month`] = month !== undefined && month !== null ? month : "";
132
+ filteredData[`${name}_year`] = year !== undefined && year !== null ? year : "";
133
+ // Handle other elements (e.g., textInput, textArea, datePicker)
134
+ } else {
135
+ filteredData[name] = formData[name] !== undefined && formData[name] !== null ? formData[name] : "";
136
+ }
137
+
138
+ // Always process conditional elements directly attached to the current element
139
+ // if (element.conditionalElements) {
140
+ // Object.assign(filteredData, getFormData(element.conditionalElements, formData));
141
+ // }
142
+ }
143
+
144
+ });
145
+
146
+
147
+ return filteredData;
148
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @module govcyLoadConfigData
3
+ * @fileoverview This module provides functions to load and manipulate configuration data for services.
4
+ * It includes functions to load service data from JSON files, handle language settings, and check for staging environments.
5
+ */
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { whatsIsMyEnvironment } from './govcyEnvVariables.mjs';
10
+ import { logger } from "./govcyLogger.mjs";
11
+
12
+
13
+ /**
14
+ * Load JSON data from `/data` by siteId
15
+ * @param {string} siteId The siteId
16
+ * @returns {Array} services - Array of JSON data
17
+ */
18
+ function loadConfigDataById(siteId) {
19
+ // const __filename = fileURLToPath(import.meta.url);
20
+ // const __dirname = path.dirname(__filename);
21
+ // const dataPath = path.join(__dirname, '..', '..', 'data'); // Adjust if needed
22
+ const dataPath = path.join(process.cwd(), 'data'); // Adjust if needed
23
+ const filePath = path.join(dataPath, `${siteId}.json`);
24
+
25
+ try {
26
+ if (!fs.existsSync(filePath)) {
27
+ logger.debug(`Service data for '${siteId}' not found.`);
28
+ return null; // Return null so caller can handle 404 response
29
+ }
30
+
31
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
32
+ } catch (error) {
33
+ logger.error(`Error loading ${siteId}.json:`, error.message);
34
+ logger.debug(`Error loading ${siteId}.json:`, error);
35
+ return null;
36
+ }
37
+ }
38
+
39
+
40
+ /**
41
+ * Load service by siteId and return a deep copy
42
+ *
43
+ * @param {string} siteId The siteId
44
+ * @param {string} lang The desired language
45
+ * @returns {Array} services - Array of JSON data
46
+ */
47
+ export function getServiceConfigData(siteId,lang) {
48
+ //Load data from source
49
+ const service = loadConfigDataById(siteId);
50
+ if (!service) {
51
+ const error = new Error('Service not found');
52
+ error.status = 404;
53
+ throw error; // Let Express catch this
54
+ }
55
+ //Handle deep copy: Deep copy to avoid mutations
56
+ let serviceCopy = JSON.parse(JSON.stringify(service));
57
+
58
+ //Handle lang: Set language based on provided `lang` parameter
59
+ if (Array.isArray(serviceCopy.site.languages)) {
60
+ if (serviceCopy.site.languages.length === 1) {
61
+ // If there's only one language, set it to that language
62
+ serviceCopy.site.lang = serviceCopy.site.languages[0].code;
63
+ serviceCopy.site.languages = undefined;
64
+ } else if (serviceCopy.site.languages.some(l => l.code === lang)) {
65
+ // If the provided lang exists in the languages array, set it
66
+ serviceCopy.site.lang = lang;
67
+ } else if (!serviceCopy.site.lang) {
68
+ // Default to 'el' if no language is set
69
+ serviceCopy.site.lang = "el";
70
+ }
71
+ } else if (!serviceCopy.site.lang) {
72
+ // Default to 'el' if languages is not defined and no language is set
73
+ serviceCopy.site.lang = "el";
74
+ }
75
+
76
+ //Handle TESTING banner: check if staging and set isTesting
77
+ serviceCopy.site.isTesting = (whatsIsMyEnvironment() === "staging");
78
+
79
+ // Add manifest path
80
+ serviceCopy.site.manifest = `/${siteId}/manifest.json`;
81
+
82
+ return serviceCopy;
83
+ }
84
+
85
+ /**
86
+ * Load page by pageUrl and return a deep copy
87
+ *
88
+ * @param {object} service The service object containing page configurations
89
+ * @param {string} pageUrl The page URL
90
+ * @returns The page configuration object
91
+ */
92
+ export function getPageConfigData(service, pageUrl) {
93
+
94
+ // Find the page by pageUrl
95
+ let page = service.pages.find(p => p.pageData.url === pageUrl);
96
+
97
+ if (!page) {
98
+ const error = new Error('Page not found');
99
+ error.status = 404;
100
+ throw error; // Let Express catch this
101
+ }
102
+
103
+ return page;
104
+ }
105
+
106
+ /**
107
+ * Get a list of available site configs with their titles.
108
+ * @returns {Array<{filename: string, title: string}>}
109
+ */
110
+ export function listAvailableSiteConfigs() {
111
+ const dataPath = path.join(process.cwd(), 'data');
112
+ let result = [];
113
+ try {
114
+ const files = fs.readdirSync(dataPath)
115
+ .filter(f => f.endsWith('.json'));
116
+
117
+ for (const file of files) {
118
+ const siteId = path.basename(file, '.json');
119
+ try {
120
+ const config = getServiceConfigData(siteId);
121
+ result.push({
122
+ filename: siteId,
123
+ title: config.site?.title || {el: "Unknown Service", en: "Unknown Service", tr: "Unknown Service"}
124
+ });
125
+ } catch (e) {
126
+ // Skip files that can't be loaded
127
+ logger.debug(`Skipping ${file}: ${e.message}`);
128
+ }
129
+ }
130
+ } catch (err) {
131
+ logger.error('Error reading data directory:', err.message);
132
+ return result; // Return empty array if there's an error
133
+ }
134
+ return result;
135
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Logs a message to the console with a given level.
3
+ * Falls back to console.log if an unknown level is used.
4
+ *
5
+ * @param {'info'|'warn'|'error'|'debug'} level - The log level.
6
+ * @param {...any} args - The content to log.
7
+ */
8
+ export function logger(level, ...args) {
9
+ const timestamp = new Date().toISOString();
10
+
11
+ const logMethods = {
12
+ info: console.log,
13
+ warn: console.warn,
14
+ error: console.error,
15
+ debug: (...msg) => {
16
+ if (process.env.DEBUG === 'true') {
17
+ console.debug(...msg);
18
+ }
19
+ }
20
+ };
21
+
22
+ const logFn = logMethods[level] || console.log;
23
+
24
+ logFn(`[${level.toUpperCase()}] [${timestamp}]`, ...args);
25
+ }
26
+ // Attach shortcut functions to the main logger function
27
+ logger.info = (...args) => logger('info', ...args);
28
+ logger.warn = (...args) => logger('warn', ...args);
29
+ logger.error = (...args) => logger('error', ...args);
30
+ logger.debug = (...args) => logger('debug', ...args);