@gov-cy/govcy-express-services 0.2.16 → 1.0.0-alpha.10

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.
@@ -0,0 +1,161 @@
1
+ import { getPageConfigData } from "../utils/govcyLoadConfigData.mjs";
2
+ import { evaluatePageConditions } from "../utils/govcyExpressions.mjs";
3
+ import { getEnvVariable, getEnvVariableBool } from "../utils/govcyEnvVariables.mjs";
4
+ import { ALLOWED_FILE_MIME_TYPES } from "../utils/govcyConstants.mjs";
5
+ import { govcyApiRequest } from "../utils/govcyApiRequest.mjs";
6
+ import * as dataLayer from "../utils/govcyDataLayer.mjs";
7
+ import { logger } from '../utils/govcyLogger.mjs';
8
+ import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
9
+ import { isMagicByteValid, pageContainsFileInput } from "../utils/govcyHandleFiles.mjs";
10
+
11
+ export function govcyFileViewHandler() {
12
+ return async (req, res, next) => {
13
+ try {
14
+ const { siteId, pageUrl, elementName } = req.params;
15
+
16
+ // Create a deep copy of the service to avoid modifying the original
17
+ let serviceCopy = req.serviceData;
18
+
19
+ // Get the download file configuration
20
+ const downloadCfg = serviceCopy?.site?.fileDownloadAPIEndpoint;
21
+ // Check if download file configuration is available
22
+ if (!downloadCfg?.url || !downloadCfg?.clientKey || !downloadCfg?.serviceId) {
23
+ return handleMiddlewareError(`File download APU configuration not found`, 404, next);
24
+ }
25
+
26
+ // Environment vars
27
+ const allowSelfSignedCerts = getEnvVariableBool("ALLOW_SELF_SIGNED_CERTIFICATES", false);
28
+ let url = getEnvVariable(downloadCfg.url || "", false);
29
+ const clientKey = getEnvVariable(downloadCfg.clientKey || "", false);
30
+ const serviceId = getEnvVariable(downloadCfg.serviceId || "", false);
31
+ const dsfGtwKey = getEnvVariable(downloadCfg?.dsfgtwApiKey || "", "");
32
+ const method = (downloadCfg?.method || "GET").toLowerCase();
33
+
34
+ // Check if the upload API is configured correctly
35
+ if (!url || !clientKey) {
36
+ return handleMiddlewareError(`Missing environment variables for upload`, 404, next);
37
+ }
38
+
39
+
40
+ // ⤵️ Find the current page based on the URL
41
+ const page = getPageConfigData(serviceCopy, pageUrl);
42
+
43
+ // deep copy the page template to avoid modifying the original
44
+ const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
45
+
46
+ // ----- Conditional logic comes here
47
+ // ✅ Skip this POST handler if the page's conditions evaluate to true (redirect away)
48
+ // const conditionResult = evaluatePageConditions(page, req.session, siteId, req);
49
+ // if (conditionResult.result === false) {
50
+ // logger.debug("⛔️ Page condition evaluated to true on POST — skipping form save and redirecting:", conditionResult);
51
+ // return handleMiddlewareError(`Page condition evaluated to true on POST — skipping form save and redirecting`, 404, next);
52
+ // }
53
+
54
+ // Validate the field: Only allow delete if the page contains a fileInput with the given name
55
+ const fileInputElement = pageContainsFileInput(pageTemplateCopy, elementName);
56
+ if (!fileInputElement) {
57
+ return handleMiddlewareError(`File input [${elementName}] not allowed on this page`, 404, next);
58
+ }
59
+
60
+ //check the reference number
61
+ const referenceNo = dataLayer.getSiteLoadDataReferenceNumber(req.session, siteId);
62
+ if (!referenceNo) {
63
+ return handleMiddlewareError(`Missing submission reference number`, 404, next);
64
+ }
65
+
66
+ //get element data
67
+ const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName)
68
+
69
+ // If the element data is not found, return an error response
70
+ if (!elementData || !elementData?.sha256 || !elementData?.fileId) {
71
+ return handleMiddlewareError(`File input [${elementName}] data not found on this page`, 404, next);
72
+ }
73
+
74
+ // Construct the URL with tag being the elementName
75
+ url += `/${encodeURIComponent(referenceNo)}/${encodeURIComponent(elementData.fileId)}/${encodeURIComponent(elementData.sha256)}`;
76
+
77
+ // Get the user
78
+ const user = dataLayer.getUser(req.session);
79
+ // Perform the upload request
80
+ const response = await govcyApiRequest(
81
+ method,
82
+ url,
83
+ {},
84
+ true,
85
+ user,
86
+ {
87
+ accept: "text/plain",
88
+ "client-key": clientKey,
89
+ "service-id": serviceId,
90
+ ...(dsfGtwKey !== "" && { "dsfgtw-api-key": dsfGtwKey })
91
+ },
92
+ 3,
93
+ allowSelfSignedCerts
94
+ );
95
+
96
+ // If not succeeded, handle error
97
+ if (!response?.Succeeded) {
98
+ return handleMiddlewareError(`fileDownloadAPIEndpoint returned succeeded false`, 500, next);
99
+ }
100
+
101
+ // Check if the response contains the expected data
102
+ if (!response?.Data?.contentType || !response?.Data?.fileName || !response?.Data?.base64) {
103
+ return handleMiddlewareError(`fileDownloadAPIEndpoint - Missing contentType, fileName or base64 in response data`, 500, next);
104
+ }
105
+
106
+ // Get filename
107
+ const filename = response?.Data?.fileName || "filename";
108
+ const fallbackFilename = asciiFallback(filename);
109
+ const utf8Filename = encodeRFC5987(filename);
110
+
111
+ // Decode base64 to binary
112
+ const fileBuffer = Buffer.from(response.Data.base64, 'base64');
113
+
114
+ // file type checks
115
+ // 1. Check declared mimetype
116
+ if (!ALLOWED_FILE_MIME_TYPES.includes(response?.Data?.contentType)) {
117
+ return handleMiddlewareError(`fileDownloadAPIEndpoint - Invalid file type (MIME not allowed)`, 500, next);
118
+ }
119
+
120
+ // 2. Check actual file content (magic bytes) matches claimed MIME type
121
+ if (!isMagicByteValid(fileBuffer, response?.Data?.contentType)) {
122
+ return handleMiddlewareError(`fileDownloadAPIEndpoint - Invalid file type (magic byte mismatch)`, 500, next);
123
+ }
124
+
125
+ // Check if Buffer is empty
126
+ if (!fileBuffer || fileBuffer.length === 0) {
127
+ return handleMiddlewareError(`fileDownloadAPIEndpoint - File is empty or invalid`, 500, next);
128
+ }
129
+ // Send the file to the browser
130
+ res.type(response?.Data?.contentType);
131
+ res.setHeader(
132
+ 'Content-Disposition',
133
+ `inline; filename="${fallbackFilename}"; filename*=UTF-8''${utf8Filename}`
134
+ );
135
+ res.send(fileBuffer);
136
+
137
+ } catch (error) {
138
+ logger.error("Error in govcyViewFileHandler middleware:", error.message);
139
+ return next(error); // Pass the error to the next middleware
140
+ }
141
+ };
142
+ }
143
+
144
+ //------------------------------------------------------------------------------
145
+ // Helper functions
146
+ // rfc5987 encoding for filename*
147
+ function encodeRFC5987(str) {
148
+ return encodeURIComponent(str)
149
+ .replace(/['()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase())
150
+ .replace(/%(7C|60|5E)/g, (m, p) => '%' + p); // | ` ^
151
+ }
152
+
153
+ // ASCII fallback for old browsers
154
+ function asciiFallback(str) {
155
+ return str
156
+ .replace(/[^\x20-\x7E]/g, '') // strip non-ASCII
157
+ .replace(/[/\\?%*:|"<>]/g, '-') // reserved chars
158
+ .replace(/[\r\n]/g, ' ') // drop newlines
159
+ .replace(/"/g, "'") // no quotes
160
+ || 'download';
161
+ }
@@ -6,6 +6,7 @@ import { logger } from "../utils/govcyLogger.mjs";
6
6
  import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
7
7
  import { getFormData } from "../utils/govcyFormHandling.mjs"
8
8
  import { evaluatePageConditions } from "../utils/govcyExpressions.mjs";
9
+ import { tempSaveIfConfigured } from "../utils/govcyTempSave.mjs";
9
10
 
10
11
 
11
12
  /**
@@ -42,7 +43,7 @@ export function govcyFormsPostHandler() {
42
43
  }
43
44
 
44
45
  // const formData = req.body; // Submitted data
45
- const formData = getFormData(formElement.params.elements, req.body); // Submitted data
46
+ const formData = getFormData(formElement.params.elements, req.body, req.session, siteId, pageUrl); // Submitted data
46
47
 
47
48
  // ☑️ Start validation from top-level form elements
48
49
  const validationErrors = validateFormElements(formElement.params.elements, formData);
@@ -59,7 +60,12 @@ export function govcyFormsPostHandler() {
59
60
 
60
61
  //⤴️ Store validated form data in session
61
62
  dataLayer.storePageData(req.session, siteId, pageUrl, formData);
62
-
63
+
64
+ // 🔄 Fire-and-forget temporary save (non-blocking)
65
+ (async () => {
66
+ try { await tempSaveIfConfigured(req.session, service, siteId); }
67
+ catch (e) { /* already logged internally */ }
68
+ })();
63
69
 
64
70
  logger.debug("✅ Form submitted successfully:", dataLayer.getPageData(req.session, siteId, pageUrl), req);
65
71
  logger.info("✅ Form submitted successfully:", req.originalUrl);
@@ -3,6 +3,8 @@ import * as govcyResources from "../resources/govcyResources.mjs";
3
3
  import * as dataLayer from "../utils/govcyDataLayer.mjs";
4
4
  import { logger } from "../utils/govcyLogger.mjs";
5
5
  import { whatsIsMyEnvironment } from '../utils/govcyEnvVariables.mjs';
6
+ import { errorResponse } from '../utils/govcyApiResponse.mjs';
7
+ import { isApiRequest } from '../utils/govcyApiDetection.mjs';
6
8
 
7
9
  /**
8
10
  * Middleware function to handle HTTP errors and render appropriate error pages.
@@ -49,9 +51,8 @@ export function govcyHttpErrorHandler(err, req, res, next) {
49
51
 
50
52
  res.status(statusCode);
51
53
 
52
- // Return JSON if the request expects it
53
- if (req.headers.accept && req.headers.accept.includes("application/json")) {
54
- return res.json({ error: message });
54
+ if (isApiRequest(req)) {
55
+ return res.status(statusCode).json(errorResponse(statusCode, message));
55
56
  }
56
57
 
57
58
  // Render an error page for non-JSON requests
@@ -0,0 +1,22 @@
1
+ import { logger } from "../utils/govcyLogger.mjs";
2
+ import { govcyLoadSubmissionDataAPIs } from "../utils/govcyLoadSubmissionDataAPIs.mjs";
3
+ /**
4
+ * Middleware to load submission data from APIs.
5
+ * This middleware fetches submission data from configured APIs and stores it in the session.
6
+ * @returns {function} Middleware function to load submission data from APIs
7
+ */
8
+ export function govcyLoadSubmissionData() {
9
+ return async (req, res, next) => {
10
+ try {
11
+ const service = req.serviceData;
12
+ // Extract siteId from request
13
+ const { siteId } = req.params;
14
+
15
+ return await govcyLoadSubmissionDataAPIs(req.session, service, siteId, next);
16
+
17
+ } catch (error) {
18
+ logger.error("Error in govcyLoadSubmissionData middleware:", error.message);
19
+ return next(error); // Pass the error to the next middleware
20
+ }
21
+ }
22
+ }
@@ -53,6 +53,9 @@ export function govcyPageHandler() {
53
53
  element.params.method = "POST";
54
54
  // ➕ Add CSRF token
55
55
  element.params.elements.push(govcyResources.csrfTokenInput(req.csrfToken()));
56
+ // // ➕ Add siteId and pageUrl to form data
57
+ // element.params.elements.push(govcyResources.siteAndPageInput(siteId, pageUrl, req.globalLang));
58
+ // ➕ Add govcyFormsJs script to the form
56
59
  element.params.elements.push(govcyResources.staticResources.elements["govcyFormsJs"]);
57
60
 
58
61
  // 🔍 Find the first button with `prototypeNavigate`
@@ -88,7 +91,7 @@ export function govcyPageHandler() {
88
91
  }
89
92
  //--------- End of Handle Validation Errors ---------
90
93
 
91
- populateFormData(element.params.elements, theData,validationErrors);
94
+ populateFormData(element.params.elements, theData,validationErrors, req.session, siteId, pageUrl, req.globalLang, null, req.query.route);
92
95
  // if there are validation errors, add an error summary
93
96
  if (validationErrors?.errorSummary?.length > 0) {
94
97
  element.params.elements.unshift(govcyResources.errorSummary(validationErrors.errorSummary));
@@ -117,3 +120,4 @@ export function govcyPageHandler() {
117
120
  }
118
121
  };
119
122
  }
123
+
@@ -0,0 +1,197 @@
1
+ // 🔍 Select all file inputs that have the .govcy-file-upload class
2
+ var fileInputs = document.querySelectorAll('input[type="file"].govcy-file-upload');
3
+
4
+ // 🔁 Loop over each file input and attach a change event listener
5
+ fileInputs.forEach(function(input) {
6
+ input.addEventListener('change', _uploadFileEventHandler);
7
+ });
8
+
9
+
10
+ /**
11
+ * Handles the upload of a file event
12
+ *
13
+ * @param {object} event The event
14
+ */
15
+ function _uploadFileEventHandler(event) {
16
+ var input = event.target;
17
+ var messages = {
18
+ "uploadSuccesful": {
19
+ "el": "Το αρχείο ανεβαστηκε",
20
+ "en": "File uploaded successfully",
21
+ "tr": "File uploaded successfully"
22
+ },
23
+ "uploadFailed": {
24
+ "el": "Αποτυχια ανεβασης",
25
+ "en": "File upload failed",
26
+ "tr": "File upload failed"
27
+ },
28
+ "uploadFailed406": {
29
+ "el": "Το επιλεγμένο αρχείο είναι κενό",
30
+ "en": "The selected file is empty",
31
+ "tr": "The selected file is empty"
32
+ },
33
+ "uploadFailed407": {
34
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι JPG, JPEG, PNG ή PDF",
35
+ "en": "The selected file must be a JPG, JPEG, PNG or PDF",
36
+ "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
37
+ },
38
+ "uploadFailed408": {
39
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι JPG, JPEG, PNG ή PDF",
40
+ "en": "The selected file must be a JPG, JPEG, PNG or PDF",
41
+ "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
42
+ },
43
+ "uploadFailed409": {
44
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι μικρότερο από 5MB",
45
+ "en": "The selected file must be smaller than 5MB",
46
+ "tr": "The selected file must be smaller than 5MB"
47
+ }
48
+ };
49
+
50
+ // 🔐 Get the CSRF token from a hidden input field (generated by your backend)
51
+ var csrfEl = document.querySelector('input[type="hidden"][name="_csrf"]');
52
+ var csrfToken = csrfEl ? csrfEl.value : '';
53
+
54
+ // 🔧 Define siteId and pageUrl (you can dynamically extract these later)
55
+ var siteId = window._govcySiteId || "";
56
+ var pageUrl = window._govcyPageUrl || "";
57
+ var lang = window._govcyLang || "el";
58
+
59
+ // 📦 Grab the selected file
60
+ var file = event.target.files[0];
61
+ var elementName = input.name; // Form field's `name` attribute
62
+ var elementId = input.id; // Form field's `id` attribute
63
+
64
+ if (!file) return; // Exit if no file was selected
65
+
66
+ // 🧵 Prepare form-data payload for the API
67
+ var formData = new FormData();
68
+ formData.append('file', file); // Attach the actual file
69
+ formData.append('elementName', elementName); // Attach the field name for backend lookup
70
+
71
+ // 🚀 CHANGED: using fetch instead of axios.post
72
+ fetch(`/apis/${siteId}/${pageUrl}/upload`, {
73
+ method: "POST",
74
+ headers: {
75
+ "X-CSRF-Token": csrfToken // 🔐 Pass CSRF token in custom header
76
+ },
77
+ body: formData
78
+ })
79
+ .then(function(response) {
80
+ // 🚀 CHANGED: fetch does not auto-throw on error codes → check manually
81
+ if (!response.ok) {
82
+ return response.json().then(function(errData) {
83
+ throw { response: { data: errData } };
84
+ });
85
+ }
86
+ return response.json();
87
+ })
88
+ .then(function(data) {
89
+ // ✅ Success response
90
+ var sha256 = data.Data.sha256;
91
+ var fileId = data.Data.fileId;
92
+
93
+ // 📝 Store returned metadata in hidden fields if needed
94
+ // document.querySelector('[name="' + elementName + 'Attachment[fileId]"]').value = fileId;
95
+ // document.querySelector('[name="' + elementName + 'Attachment[sha256]"]').value = sha256;
96
+
97
+ // Render the file view
98
+ _renderFileElement("fileView", elementId, elementName, fileId, sha256, null);
99
+
100
+ // Accessibility: Update ARIA live region with success message
101
+ var statusRegion = document.getElementById('_govcy-upload-status');
102
+ if (statusRegion) {
103
+ setTimeout(function() {
104
+ statusRegion.textContent = messages.uploadSuccesful[lang];
105
+ }, 200);
106
+ setTimeout(function() {
107
+ statusRegion.textContent = '';
108
+ }, 5000);
109
+ }
110
+ })
111
+ .catch(function(err) {
112
+ // ⚠️ Show an error message if upload fails
113
+ var errorMessage = messages.uploadFailed;
114
+ var errorCode = err && err.response && err.response.data && err.response.data.ErrorCode;
115
+
116
+ if (errorCode === 406 || errorCode === 407 || errorCode === 408 || errorCode === 409) {
117
+ errorMessage = messages["uploadFailed" + errorCode];
118
+ }
119
+
120
+ // Render the file input with error
121
+ _renderFileElement("fileInput", elementId, elementName, "", "", errorMessage);
122
+
123
+ // Re-bind the file input's change handler
124
+ var newInput = document.getElementById(elementId);
125
+ if (newInput) {
126
+ newInput.addEventListener('change', _uploadFileEventHandler);
127
+ }
128
+
129
+ // Accessibility: Focus on the form field
130
+ document.getElementById(elementId)?.focus();
131
+ });
132
+ }
133
+
134
+
135
+ /**
136
+ * Renders a file element in the DOM
137
+ *
138
+ * @param {string} elementState The element state. Can be "fileInput" or "fileView"
139
+ * @param {string} elementId The element id
140
+ * @param {string} elementName The element name
141
+ * @param {string} fileId The file id
142
+ * @param {string} sha256 The sha256
143
+ * @param {object} errorMessage The error message in all supported languages
144
+ */
145
+ function _renderFileElement(elementState, elementId, elementName, fileId, sha256, errorMessage) {
146
+
147
+ // Grab the query string part (?foo=bar&route=something)
148
+ var queryString = window.location.search;
149
+ // Parse it
150
+ var params = new URLSearchParams(queryString);
151
+ // Get the "route" value (null if not present)
152
+ var route = params.get("route");
153
+
154
+ // Create an instance of GovcyFrontendRendererBrowser
155
+ var renderer = new GovcyFrontendRendererBrowser();
156
+ var lang = window._govcyLang || "el";
157
+ // Define the input data
158
+ var inputData =
159
+ {
160
+ "site": {
161
+ "lang": lang
162
+ }
163
+ };
164
+ var fileInputMap = JSON.parse(JSON.stringify(window._govcyFileInputs));
165
+ var fileElement = fileInputMap[elementName];
166
+ fileElement.element = elementState;
167
+ if (errorMessage != null) fileElement.params.error = errorMessage;
168
+ if (fileId != null) fileElement.params.fileId = fileId;
169
+ if (sha256 != null) fileElement.params.sha256 = sha256;
170
+ if (elementState == "fileView") {
171
+ fileElement.params.visuallyHiddenText = fileElement.params.label;
172
+ // TODO: Also need to set the `view` and `download` URLs
173
+ fileElement.params.viewHref = "/" + window._govcySiteId + "/" + window._govcyPageUrl + "/view-file/" + elementName;
174
+ fileElement.params.viewTarget = "_blank";
175
+ fileElement.params.deleteHref = "/" + window._govcySiteId + "/" + window._govcyPageUrl + "/delete-file/" + elementName
176
+ + (route !== null ? "?route=" + encodeURIComponent(route) : "");
177
+ }
178
+ // Construct the JSONTemplate
179
+ var JSONTemplate = {
180
+ "elements": [fileElement]
181
+ };
182
+
183
+ //render HTML into string
184
+ var renderedHtml = renderer.renderFromJSON(JSONTemplate,inputData);
185
+ var outerElement = document.getElementById(`${elementId}-outer-control`)
186
+ || document.getElementById(`${elementId}-input-control`)
187
+ || document.getElementById(`${elementId}-view-control`);
188
+
189
+ if (outerElement) {
190
+ //remove all classes from outerElement
191
+ outerElement.className = "";
192
+ //set the id of the outerElement to `${elementId}-outer-control`
193
+ outerElement.id = `${elementId}-outer-control`;
194
+ //update DOM and initialize the JS components
195
+ renderer.updateDOMAndInitialize(`${elementId}-outer-control`, renderedHtml);
196
+ }
197
+ }
@@ -1,20 +1,31 @@
1
1
  document.addEventListener("DOMContentLoaded", function () {
2
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);
3
+ // CHANGED: NodeList.prototype.forEach is not in IE11 → use Array.prototype.forEach.call
4
+ // CHANGED: Arrow function → function
5
+ Array.prototype.forEach.call(
6
+ document.querySelectorAll('.govcy-radio-input[data-aria-controls]:checked'),
7
+ function (radio) { // CHANGED: arrow → function
8
+ // CHANGED: const → var (ES5)
9
+ var targetId = radio.getAttribute('data-aria-controls');
10
+ var targetElement = document.getElementById(targetId);
6
11
 
7
- if (targetElement) {
8
- targetElement.classList.remove('govcy-radio__conditional--hidden');
12
+ if (targetElement) {
13
+ targetElement.classList.remove('govcy-radio__conditional--hidden');
14
+ }
9
15
  }
10
- });
16
+ );
17
+
11
18
  // --- Disable submit button after form submission ---
12
- document.querySelectorAll('form').forEach(form => {
19
+ // CHANGED: NodeList.forEach Array.prototype.forEach.call
20
+ Array.prototype.forEach.call(document.querySelectorAll('form'), function (form) { // CHANGED
13
21
  form.addEventListener('submit', function (e) {
14
- const submitButton = form.querySelector('[type="submit"]');
22
+ // CHANGED: const var (ES5)
23
+ var submitButton = form.querySelector('[type="submit"]');
15
24
  if (submitButton) {
16
25
  submitButton.disabled = true;
17
26
  submitButton.setAttribute('aria-disabled', 'true');
27
+ // (Optional) announce busy state for AT:
28
+ // submitButton.setAttribute('aria-busy', 'true'); // CHANGED: optional a11y improvement
18
29
  }
19
30
  });
20
31
  });
@@ -6,6 +6,11 @@ export const staticResources = {
6
6
  el: "Υποβολή",
7
7
  tr: "Gönder"
8
8
  },
9
+ continue: {
10
+ en: "Continue",
11
+ el: "Συνέχεια",
12
+ tr: "Continue"
13
+ },
9
14
  cancel: {
10
15
  en: "Cancel",
11
16
  el: "Ακύρωση",
@@ -100,6 +105,46 @@ export const staticResources = {
100
105
  en: "We have received your request. ",
101
106
  el: "Έχουμε λάβει την αίτησή σας. ",
102
107
  tr: "We have received your request. "
108
+ },
109
+ fileUploaded : {
110
+ en: "File uploaded",
111
+ el: "Το αρχείο ανεβάστηκε",
112
+ tr: "File uploaded"
113
+ },
114
+ fileNotUploaded : {
115
+ en: "File has not been uploaded. ",
116
+ el: "Το αρχείο δεν ανεβάστηκε. ",
117
+ tr: "File has not been uploaded. "
118
+ },
119
+ fileYouHaveUploaded : {
120
+ en: "You have uploaded the file for \"{{file}}\"",
121
+ el: "Έχετε ανεβάσει το αρχείο \"{{file}}\"",
122
+ tr: "You have uploaded the file for \"{{file}}\""
123
+ },
124
+ deleteFileTitle : {
125
+ en: "Are you sure you want to delete the file \"{{file}}\"? ",
126
+ el: "Είστε σίγουροι ότι θέλετε να διαγράψετε το αρχείο \"{{file}}\";",
127
+ tr: "Are you sure you want to delete the file \"{{file}}\"? "
128
+ },
129
+ deleteYesOption: {
130
+ el:"Ναι, θέλω να διαγράψω το αρχείο",
131
+ en:"Yes, I want to delete this file",
132
+ tr:"Yes, I want to delete this file"
133
+ },
134
+ deleteNoOption: {
135
+ el:"Όχι, δεν θέλω να διαγράψω το αρχείο",
136
+ en:"No, I don't want to delete this file",
137
+ tr:"No, I don't want to delete this file"
138
+ },
139
+ deleteFileValidationError: {
140
+ en: "Select if you want to delete the file",
141
+ el: "Επιλέξτε αν θέλετε να διαγράψετε το αρχείο",
142
+ tr: "Select if you want to delete the file"
143
+ },
144
+ viewFile: {
145
+ en: "View file",
146
+ el: "Προβολή αρχείου",
147
+ tr: "View file"
103
148
  }
104
149
  },
105
150
  //remderer sections
@@ -113,9 +158,9 @@ export const staticResources = {
113
158
  element: "htmlElement",
114
159
  params: {
115
160
  text: {
116
- en: `<script src="/js/govcyForms.js"></script>`,
117
- el: `<script src="/js/govcyForms.js"></script>`,
118
- tr: `<script src="/js/govcyForms.js"></script>`
161
+ en: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
162
+ el: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
163
+ tr: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`
119
164
  }
120
165
  }
121
166
  },
@@ -192,6 +237,27 @@ export function csrfTokenInput(csrfToken) {
192
237
  };
193
238
  }
194
239
 
240
+ /**
241
+ * Get the site and page input elements
242
+ * @param {string} siteId The site id
243
+ * @param {string} pageUrl The page url
244
+ * @param {string} lang The page language
245
+ * @returns {object} htmlElement with the site and page inputs
246
+ */
247
+ export function siteAndPageInput(siteId, pageUrl, lang = "el") {
248
+ const siteAndPageInputs = `<input type="hidden" name="_siteId" value="${siteId}"><input type="hidden" name="_pageUrl" value="${pageUrl}"><input type="hidden" name="_lang" value="${lang}">`;
249
+ return {
250
+ element: "htmlElement",
251
+ params: {
252
+ text: {
253
+ en: siteAndPageInputs,
254
+ el: siteAndPageInputs,
255
+ tr: siteAndPageInputs
256
+ }
257
+ }
258
+ };
259
+ }
260
+
195
261
  /**
196
262
  * Error page template
197
263
  * @param {object} title the title text element
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Determines if a request is targeting an API endpoint.
3
+ * Currently matches:
4
+ * - Accept header with application/json
5
+ * - URLs ending with /upload or /download under a site/page structure
6
+ *
7
+ * @param {object} req - Express request object
8
+ * @returns {boolean}
9
+ */
10
+ export function isApiRequest(req) {
11
+ const acceptJson = (req.headers?.accept || "").toLowerCase().includes("application/json");
12
+
13
+ const apiUrlPattern = /^\/apis\/[^/]+\/[^/]+\/(upload|download)$/;
14
+ const isStructuredApiUrl = apiUrlPattern.test(req.originalUrl || req.url);
15
+
16
+ return acceptJson || isStructuredApiUrl;
17
+ }