@gov-cy/govcy-express-services 1.0.0-alpha.7 → 1.0.0-alpha.8
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/package.json +1 -1
- package/src/middleware/govcyUpload.mjs +1 -1
- package/src/public/js/govcyFiles.js +135 -112
- package/src/resources/govcyResources.mjs +10 -0
- package/src/utils/govcyFormHandling.mjs +21 -10
- package/src/utils/govcyHandleFiles.mjs +16 -1
- package/src/utils/govcySubmitData.mjs +27 -0
- package/src/utils/govcyValidator.mjs +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gov-cy/govcy-express-services",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.8",
|
|
4
4
|
"description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
|
|
5
5
|
"author": "DMRID - DSF Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@ export const govcyUploadMiddleware = [
|
|
|
28
28
|
|
|
29
29
|
if (result.status !== 200) {
|
|
30
30
|
logger.error("Upload failed", result);
|
|
31
|
-
return res.status(result.status).json(errorResponse(result.
|
|
31
|
+
return res.status(result.status).json(errorResponse(result.dataStatus, result.errorMessage || 'File upload failed'));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return res.json(successResponse(result.data));
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// 🔍 Select all file inputs that have the .govcy-file-upload class
|
|
2
|
-
|
|
2
|
+
var fileInputs = document.querySelectorAll('input[type="file"].govcy-file-upload');
|
|
3
3
|
|
|
4
4
|
// 🔁 Loop over each file input and attach a change event listener
|
|
5
|
-
fileInputs.forEach(input
|
|
6
|
-
input.addEventListener('change',
|
|
7
|
-
|
|
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
|
+
async function _uploadFileEventHandler(event) {
|
|
16
|
+
var input = event.target;
|
|
17
|
+
var messages = {
|
|
8
18
|
"uploadSuccesful": {
|
|
9
19
|
"el": "Το αρχείο ανεβαστηκε",
|
|
10
20
|
"en": "File uploaded successfully",
|
|
@@ -14,139 +24,152 @@ fileInputs.forEach(input => {
|
|
|
14
24
|
"el": "Αποτυχια ανεβασης",
|
|
15
25
|
"en": "File upload failed",
|
|
16
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"
|
|
17
47
|
}
|
|
18
48
|
};
|
|
19
49
|
// 🔐 Get the CSRF token from a hidden input field (generated by your backend)
|
|
20
|
-
|
|
50
|
+
var csrfEl = document.querySelector('input[type="hidden"][name="_csrf"]');
|
|
51
|
+
var csrfToken = csrfEl ? csrfEl.value : '';
|
|
52
|
+
|
|
21
53
|
// 🔧 Define siteId and pageUrl (you can dynamically extract these later)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
54
|
+
var siteId = window._govcySiteId || "";
|
|
55
|
+
var pageUrl = window._govcyPageUrl || "";
|
|
56
|
+
var lang = window._govcyLang || "el";
|
|
25
57
|
// 📦 Grab the selected file
|
|
26
|
-
|
|
27
|
-
|
|
58
|
+
var file = event.target.files[0];
|
|
59
|
+
var elementName = input.name; // Form field's `name` attribute
|
|
60
|
+
var elementId = input.id; // Form field's `id` attribute
|
|
28
61
|
|
|
29
62
|
if (!file) return; // Exit if no file was selected
|
|
30
63
|
|
|
31
64
|
// 🧵 Prepare form-data payload for the API
|
|
32
|
-
|
|
65
|
+
var formData = new FormData();
|
|
33
66
|
formData.append('file', file); // Attach the actual file
|
|
34
67
|
formData.append('elementName', elementName); // Attach the field name for backend lookup
|
|
35
68
|
|
|
36
69
|
try {
|
|
37
70
|
// 🚀 Send file to the backend upload API
|
|
38
|
-
|
|
71
|
+
var response = await axios.post(`/apis/${siteId}/${pageUrl}/upload`, formData, {
|
|
39
72
|
headers: {
|
|
40
73
|
'X-CSRF-Token': csrfToken // 🔐 Pass CSRF token in custom header
|
|
41
74
|
}
|
|
42
75
|
});
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
var sha256 = response.data.Data.sha256;
|
|
78
|
+
var fileId = response.data.Data.fileId;
|
|
45
79
|
|
|
46
80
|
// 📝 Store returned metadata in hidden fields for submission with the form
|
|
47
|
-
document.querySelector(
|
|
48
|
-
document.querySelector(
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"lang": lang
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const fileInputMap = window._govcyFileInputs || {};
|
|
62
|
-
let fileElement = fileInputMap[elementName];
|
|
63
|
-
fileElement.element = "fileView";
|
|
64
|
-
fileElement.params.fileId = fileId;
|
|
65
|
-
fileElement.params.sha256 = sha256;
|
|
66
|
-
fileElement.params.visuallyHiddenText = fileElement.params.label;
|
|
67
|
-
fileElement.params.error = null;
|
|
68
|
-
// TODO: Also need to set the `view` and `download` URLs
|
|
69
|
-
fileElement.params.viewHref = "#viewHref";
|
|
70
|
-
fileElement.params.deleteHref = "#deleteHref";
|
|
71
|
-
// Construct the JSONTemplate
|
|
72
|
-
const JSONTemplate = {
|
|
73
|
-
"elements": [fileElement]
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
//render HTML into string
|
|
77
|
-
let renderedHtml = renderer.renderFromJSON(JSONTemplate,inputData);
|
|
78
|
-
// look for element with id `${elementName}-outer-control`
|
|
79
|
-
// if not found look for element with id `${elementName}-input-control`
|
|
80
|
-
// if not found look for element with id `${elementName}-view-control`
|
|
81
|
-
var outerElement = document.getElementById(`${elementName}-outer-control`)
|
|
82
|
-
|| document.getElementById(`${elementName}-input-control`)
|
|
83
|
-
|| document.getElementById(`${elementName}-view-control`);
|
|
84
|
-
|
|
85
|
-
if (outerElement) {
|
|
86
|
-
//remove all classes from outerElement
|
|
87
|
-
outerElement.className = "";
|
|
88
|
-
//set the id of the outerElement to `${elementName}-outer-control`
|
|
89
|
-
outerElement.id = `${elementName}-outer-control`;
|
|
90
|
-
//update DOM and initialize the JS components
|
|
91
|
-
renderer.updateDOMAndInitialize(`${elementName}-outer-control`, renderedHtml);
|
|
92
|
-
}
|
|
93
|
-
// ✅ Update ARIA live region with success message
|
|
94
|
-
const statusRegion = document.getElementById('_govcy-upload-status');
|
|
95
|
-
if (statusRegion) {
|
|
81
|
+
// document.querySelector('[name="' + elementName + 'Attachment[fileId]"]').value = fileId;
|
|
82
|
+
// document.querySelector('[name="' + elementName + 'Attachment[sha256]"]').value = sha256;
|
|
83
|
+
|
|
84
|
+
// Render the file view
|
|
85
|
+
_renderFileElement("fileView", elementId, elementName, fileId, sha256, null);
|
|
86
|
+
|
|
87
|
+
// Accessibility: Update ARIA live region with success message
|
|
88
|
+
var statusRegion = document.getElementById('_govcy-upload-status');
|
|
89
|
+
if (statusRegion) {
|
|
90
|
+
setTimeout(function() {
|
|
96
91
|
statusRegion.textContent = messages.uploadSuccesful[lang];
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
92
|
+
}, 200)
|
|
93
|
+
setTimeout(function() {
|
|
94
|
+
statusRegion.textContent = '';
|
|
95
|
+
}, 5000);
|
|
96
|
+
}
|
|
101
97
|
// alert('✅ File uploaded successfully');
|
|
102
98
|
|
|
103
99
|
} catch (err) {
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"site": {
|
|
111
|
-
"lang": lang
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
const fileInputMap = window._govcyFileInputs || {};
|
|
115
|
-
let fileElement = fileInputMap[elementName];
|
|
116
|
-
fileElement.element = "fileInput";
|
|
117
|
-
fileElement.params.fileId = "";
|
|
118
|
-
fileElement.params.sha256 = ""
|
|
119
|
-
fileElement.params.error = messages.uploadFailed;
|
|
120
|
-
|
|
121
|
-
// Construct the JSONTemplate
|
|
122
|
-
const JSONTemplate = {
|
|
123
|
-
"elements": [fileElement]
|
|
124
|
-
};
|
|
125
|
-
//render HTML into string
|
|
126
|
-
let renderedHtml = renderer.renderFromJSON(JSONTemplate,inputData);
|
|
127
|
-
var outerElement = document.getElementById(`${elementName}-outer-control`)
|
|
128
|
-
|| document.getElementById(`${elementName}-input-control`)
|
|
129
|
-
|| document.getElementById(`${elementName}-view-control`);
|
|
130
|
-
|
|
131
|
-
if (outerElement) {
|
|
132
|
-
//remove all classes from outerElement
|
|
133
|
-
outerElement.className = "";
|
|
134
|
-
//set the id of the outerElement to `${elementName}-outer-control`
|
|
135
|
-
outerElement.id = `${elementName}-outer-control`;
|
|
136
|
-
//update DOM and initialize the JS components
|
|
137
|
-
renderer.updateDOMAndInitialize(`${elementName}-outer-control`, renderedHtml);
|
|
138
|
-
//TODO: Kamran need to figure a way to re register the DOM event on change
|
|
100
|
+
// ⚠️ Show an error message if upload fails
|
|
101
|
+
var errorMessage = messages.uploadFailed;
|
|
102
|
+
var errorCode = err && err.response && err.response.data && err.response.data.ErrorCode;
|
|
103
|
+
|
|
104
|
+
if (errorCode === 406 || errorCode === 407 || errorCode === 408 || errorCode === 409) {
|
|
105
|
+
errorMessage = messages["uploadFailed" + errorCode];
|
|
139
106
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
107
|
+
|
|
108
|
+
// Render the file input with error
|
|
109
|
+
_renderFileElement("fileInput", elementId, elementName, "","", errorMessage);
|
|
110
|
+
|
|
111
|
+
// Re-bind the file input's change handler
|
|
112
|
+
var newInput = document.getElementById(elementId);
|
|
113
|
+
if (newInput) {
|
|
114
|
+
newInput.addEventListener('change', _uploadFileEventHandler);
|
|
147
115
|
}
|
|
148
|
-
|
|
149
|
-
//
|
|
116
|
+
|
|
117
|
+
// Accessibility: Focus on the form field
|
|
118
|
+
document.getElementById(elementId)?.focus();
|
|
119
|
+
|
|
150
120
|
}
|
|
151
|
-
|
|
152
|
-
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Renders a file element in the DOM
|
|
125
|
+
*
|
|
126
|
+
* @param {string} elementState The element state. Can be "fileInput" or "fileView"
|
|
127
|
+
* @param {string} elementId The element id
|
|
128
|
+
* @param {string} elementName The element name
|
|
129
|
+
* @param {string} fileId The file id
|
|
130
|
+
* @param {string} sha256 The sha256
|
|
131
|
+
* @param {object} errorMessage The error message in all supported languages
|
|
132
|
+
*/
|
|
133
|
+
function _renderFileElement(elementState, elementId, elementName, fileId, sha256, errorMessage) {
|
|
134
|
+
// Create an instance of GovcyFrontendRendererBrowser
|
|
135
|
+
var renderer = new GovcyFrontendRendererBrowser();
|
|
136
|
+
var lang = window._govcyLang || "el";
|
|
137
|
+
// Define the input data
|
|
138
|
+
var inputData =
|
|
139
|
+
{
|
|
140
|
+
"site": {
|
|
141
|
+
"lang": lang
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var fileInputMap = window._govcyFileInputs || {};
|
|
145
|
+
var fileElement = fileInputMap[elementName];
|
|
146
|
+
fileElement.element = elementState;
|
|
147
|
+
if (errorMessage != null) fileElement.params.error = errorMessage;
|
|
148
|
+
if (fileId != null) fileElement.params.fileId = fileId;
|
|
149
|
+
if (sha256 != null) fileElement.params.sha256 = sha256;
|
|
150
|
+
if (elementState == "fileView") {
|
|
151
|
+
fileElement.params.visuallyHiddenText = fileElement.params.label;
|
|
152
|
+
// TODO: Also need to set the `view` and `download` URLs
|
|
153
|
+
fileElement.params.viewHref = "#viewHref";
|
|
154
|
+
fileElement.params.deleteHref = "#deleteHref";
|
|
155
|
+
}
|
|
156
|
+
// Construct the JSONTemplate
|
|
157
|
+
var JSONTemplate = {
|
|
158
|
+
"elements": [fileElement]
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
//render HTML into string
|
|
162
|
+
var renderedHtml = renderer.renderFromJSON(JSONTemplate,inputData);
|
|
163
|
+
var outerElement = document.getElementById(`${elementId}-outer-control`)
|
|
164
|
+
|| document.getElementById(`${elementId}-input-control`)
|
|
165
|
+
|| document.getElementById(`${elementId}-view-control`);
|
|
166
|
+
|
|
167
|
+
if (outerElement) {
|
|
168
|
+
//remove all classes from outerElement
|
|
169
|
+
outerElement.className = "";
|
|
170
|
+
//set the id of the outerElement to `${elementId}-outer-control`
|
|
171
|
+
outerElement.id = `${elementId}-outer-control`;
|
|
172
|
+
//update DOM and initialize the JS components
|
|
173
|
+
renderer.updateDOMAndInitialize(`${elementId}-outer-control`, renderedHtml);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -100,6 +100,16 @@ export const staticResources = {
|
|
|
100
100
|
en: "We have received your request. ",
|
|
101
101
|
el: "Έχουμε λάβει την αίτησή σας. ",
|
|
102
102
|
tr: "We have received your request. "
|
|
103
|
+
},
|
|
104
|
+
fileUploaded : {
|
|
105
|
+
en: "File has been uploaded. ",
|
|
106
|
+
el: "Το αρχείο ανεβάστηκε. ",
|
|
107
|
+
tr: "File has been uploaded. "
|
|
108
|
+
},
|
|
109
|
+
fileNotUploaded : {
|
|
110
|
+
en: "File has not been uploaded. ",
|
|
111
|
+
el: "Το αρχείο δεν ανεβάστηκε. ",
|
|
112
|
+
tr: "File has not been uploaded. "
|
|
103
113
|
}
|
|
104
114
|
},
|
|
105
115
|
//remderer sections
|
|
@@ -18,9 +18,12 @@ import * as dataLayer from "./govcyDataLayer.mjs";
|
|
|
18
18
|
* @param {string} pageUrl The page URL
|
|
19
19
|
* @param {string} lang The language
|
|
20
20
|
*/
|
|
21
|
-
export function populateFormData(formElements, theData, validationErrors, store = {}, siteId = "", pageUrl = "", lang = "el") {
|
|
21
|
+
export function populateFormData(formElements, theData, validationErrors, store = {}, siteId = "", pageUrl = "", lang = "el", fileInputElements = null) {
|
|
22
22
|
const inputElements = ALLOWED_FORM_ELEMENTS;
|
|
23
|
-
|
|
23
|
+
const isRootCall = !fileInputElements;
|
|
24
|
+
if (isRootCall) {
|
|
25
|
+
fileInputElements = {};
|
|
26
|
+
}
|
|
24
27
|
// Recursively populate form data with session data
|
|
25
28
|
formElements.forEach(element => {
|
|
26
29
|
if (inputElements.includes(element.element)) {
|
|
@@ -62,7 +65,9 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
62
65
|
} else if (element.element === "fileInput") {
|
|
63
66
|
// For fileInput, we change the element.element to "fileView" and set the
|
|
64
67
|
// fileId and sha256 from the session store
|
|
65
|
-
|
|
68
|
+
// unneeded handle of `Attachment` at the end
|
|
69
|
+
// const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, fieldName + "Attachment");
|
|
70
|
+
const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, fieldName);
|
|
66
71
|
// TODO: Ask Andreas how to handle empty file inputs
|
|
67
72
|
if (fileData) {
|
|
68
73
|
element.element = "fileView";
|
|
@@ -102,7 +107,7 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
102
107
|
if (element.element === "radios" && element.params.items) {
|
|
103
108
|
element.params.items.forEach(item => {
|
|
104
109
|
if (item.conditionalElements) {
|
|
105
|
-
populateFormData(item.conditionalElements, theData, validationErrors);
|
|
110
|
+
populateFormData(item.conditionalElements, theData, validationErrors,store, siteId , pageUrl, lang, fileInputElements);
|
|
106
111
|
|
|
107
112
|
// Check if any conditional element has an error and add to the parent "conditionalHasErrors": true
|
|
108
113
|
if (item.conditionalElements.some(condEl => condEl.params?.error)) {
|
|
@@ -113,7 +118,7 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
113
118
|
}
|
|
114
119
|
});
|
|
115
120
|
// add file input elements's definition in js object
|
|
116
|
-
if (fileInputElements
|
|
121
|
+
if (isRootCall && Object.keys(fileInputElements).length > 0) {
|
|
117
122
|
const scriptTag = `
|
|
118
123
|
<script type="text/javascript">
|
|
119
124
|
window._govcyFileInputs = ${JSON.stringify(fileInputElements)};
|
|
@@ -121,7 +126,7 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
121
126
|
window._govcyPageUrl = "${pageUrl}";
|
|
122
127
|
window._govcyLang = "${lang}";
|
|
123
128
|
</script>
|
|
124
|
-
<div id="_govcy-upload-status" class="govcy-visually-hidden" role="status" aria-live="
|
|
129
|
+
<div id="_govcy-upload-status" class="govcy-visually-hidden" role="status" aria-live="assertive"></div>
|
|
125
130
|
<div id="_govcy-upload-error" class="govcy-visually-hidden" role="alert" aria-live="assertive"></div>
|
|
126
131
|
`.trim();
|
|
127
132
|
formElements.push({
|
|
@@ -165,7 +170,7 @@ export function getFormData(elements, formData, store = {}, siteId = "", pageUrl
|
|
|
165
170
|
if (item.conditionalElements) {
|
|
166
171
|
Object.assign(
|
|
167
172
|
filteredData,
|
|
168
|
-
getFormData(item.conditionalElements, formData)
|
|
173
|
+
getFormData(item.conditionalElements, formData, store, siteId, pageUrl)
|
|
169
174
|
);
|
|
170
175
|
}
|
|
171
176
|
});
|
|
@@ -184,12 +189,18 @@ export function getFormData(elements, formData, store = {}, siteId = "", pageUrl
|
|
|
184
189
|
} else if (element.element === "fileInput") {
|
|
185
190
|
// fileInput elements are already stored in the store when it was uploaded
|
|
186
191
|
// so we just need to check if the file exists in the dataLayer in the store and add it the filteredData
|
|
187
|
-
|
|
192
|
+
// unneeded handle of `Attachment` at the end
|
|
193
|
+
// const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, name + "Attachment");
|
|
194
|
+
const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, name);
|
|
188
195
|
if (fileData) {
|
|
189
|
-
|
|
196
|
+
// unneeded handle of `Attachment` at the end
|
|
197
|
+
// filteredData[name + "Attachment"] = fileData;
|
|
198
|
+
filteredData[name] = fileData;
|
|
190
199
|
} else {
|
|
191
200
|
//TODO: Ask Andreas how to handle empty file inputs
|
|
192
|
-
|
|
201
|
+
// unneeded handle of `Attachment` at the end
|
|
202
|
+
// filteredData[name + "Attachment"] = ""; // or handle as needed
|
|
203
|
+
filteredData[name] = ""; // or handle as needed
|
|
193
204
|
}
|
|
194
205
|
// Handle other elements (e.g., textInput, textArea, datePicker)
|
|
195
206
|
} else {
|
|
@@ -27,6 +27,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
27
27
|
if (!file || !elementName) {
|
|
28
28
|
return {
|
|
29
29
|
status: 400,
|
|
30
|
+
dataStatus: 400,
|
|
30
31
|
errorMessage: 'Missing file or element name'
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -37,6 +38,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
37
38
|
if (!uploadCfg?.url || !uploadCfg?.clientKey || !uploadCfg?.serviceId) {
|
|
38
39
|
return {
|
|
39
40
|
status: 400,
|
|
41
|
+
dataStatus: 401,
|
|
40
42
|
errorMessage: 'Missing upload configuration'
|
|
41
43
|
};
|
|
42
44
|
}
|
|
@@ -53,6 +55,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
53
55
|
if (!url || !clientKey) {
|
|
54
56
|
return {
|
|
55
57
|
status: 400,
|
|
58
|
+
dataStatus: 402,
|
|
56
59
|
errorMessage: 'Missing environment variables for upload'
|
|
57
60
|
};
|
|
58
61
|
}
|
|
@@ -67,6 +70,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
67
70
|
if (!page?.pageTemplate) {
|
|
68
71
|
return {
|
|
69
72
|
status: 400,
|
|
73
|
+
dataStatus: 403,
|
|
70
74
|
errorMessage: 'Invalid page configuration'
|
|
71
75
|
};
|
|
72
76
|
}
|
|
@@ -77,6 +81,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
77
81
|
if (conditionResult.result === false) {
|
|
78
82
|
return {
|
|
79
83
|
status: 403,
|
|
84
|
+
dataStatus: 404,
|
|
80
85
|
errorMessage: 'This page is skipped by conditional logic'
|
|
81
86
|
};
|
|
82
87
|
}
|
|
@@ -88,6 +93,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
88
93
|
if (!isAllowed) {
|
|
89
94
|
return {
|
|
90
95
|
status: 403,
|
|
96
|
+
dataStatus: 405,
|
|
91
97
|
errorMessage: `File input [${elementName}] not allowed on this page`
|
|
92
98
|
};
|
|
93
99
|
}
|
|
@@ -96,6 +102,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
96
102
|
if (file.size === 0) {
|
|
97
103
|
return {
|
|
98
104
|
status: 400,
|
|
105
|
+
dataStatus: 406,
|
|
99
106
|
errorMessage: 'Uploaded file is empty'
|
|
100
107
|
};
|
|
101
108
|
}
|
|
@@ -105,6 +112,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
105
112
|
if (!ALLOWED_FILE_MIME_TYPES.includes(file.mimetype)) {
|
|
106
113
|
return {
|
|
107
114
|
status: 400,
|
|
115
|
+
dataStatus: 407,
|
|
108
116
|
errorMessage: 'Invalid file type (MIME not allowed)'
|
|
109
117
|
};
|
|
110
118
|
}
|
|
@@ -113,6 +121,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
113
121
|
if (!isMagicByteValid(file.buffer, file.mimetype)) {
|
|
114
122
|
return {
|
|
115
123
|
status: 400,
|
|
124
|
+
dataStatus: 408,
|
|
116
125
|
errorMessage: 'Invalid file type (magic byte mismatch)'
|
|
117
126
|
};
|
|
118
127
|
}
|
|
@@ -121,6 +130,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
121
130
|
if (file.size > ALLOWED_FILE_SIZE_MB * 1024 * 1024) {
|
|
122
131
|
return {
|
|
123
132
|
status: 400,
|
|
133
|
+
dataStatus: 409,
|
|
124
134
|
errorMessage: 'File exceeds allowed size'
|
|
125
135
|
};
|
|
126
136
|
}
|
|
@@ -161,6 +171,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
161
171
|
if (!response?.Succeeded) {
|
|
162
172
|
return {
|
|
163
173
|
status: 500,
|
|
174
|
+
dataStatus: 410,
|
|
164
175
|
errorMessage: `${response?.ErrorCode} - ${response?.ErrorMessage} - fileUploadAPIEndpoint returned succeeded false`
|
|
165
176
|
};
|
|
166
177
|
}
|
|
@@ -169,13 +180,16 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
169
180
|
if (!response?.Data?.fileId || !response?.Data?.sha256) {
|
|
170
181
|
return {
|
|
171
182
|
status: 500,
|
|
183
|
+
dataStatus: 411,
|
|
172
184
|
errorMessage: 'Missing fileId or sha256 in response'
|
|
173
185
|
};
|
|
174
186
|
}
|
|
175
187
|
|
|
176
188
|
// ✅ Success
|
|
177
189
|
// Store the file metadata in the session store
|
|
178
|
-
|
|
190
|
+
// unneeded handle of `Attachment` at the end
|
|
191
|
+
// dataLayer.storePageDataElement(store, siteId, pageUrl, elementName+"Attachment", {
|
|
192
|
+
dataLayer.storePageDataElement(store, siteId, pageUrl, elementName, {
|
|
179
193
|
sha256: response.Data.sha256,
|
|
180
194
|
fileId: response.Data.fileId,
|
|
181
195
|
});
|
|
@@ -195,6 +209,7 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
195
209
|
} catch (err) {
|
|
196
210
|
return {
|
|
197
211
|
status: 500,
|
|
212
|
+
dataStatus: 500,
|
|
198
213
|
errorMessage: 'Upload failed' + (err.message ? `: ${err.message}` : ''),
|
|
199
214
|
};
|
|
200
215
|
}
|
|
@@ -66,6 +66,12 @@ export function prepareSubmissionData(req, siteId, service) {
|
|
|
66
66
|
// Store in submissionData
|
|
67
67
|
submissionData[pageUrl].formData[elId] = value;
|
|
68
68
|
|
|
69
|
+
// handle fileInput
|
|
70
|
+
if (elType === "fileInput") {
|
|
71
|
+
// change the name of the key to include "Attachment" at the end but not have the original key
|
|
72
|
+
submissionData[pageUrl].formData[elId + "Attachment"] = value;
|
|
73
|
+
delete submissionData[pageUrl].formData[elId];
|
|
74
|
+
}
|
|
69
75
|
|
|
70
76
|
// 🔄 If radios with conditionalElements, walk ALL options
|
|
71
77
|
if (elType === "radios" && Array.isArray(element.params?.items)) {
|
|
@@ -85,6 +91,12 @@ export function prepareSubmissionData(req, siteId, service) {
|
|
|
85
91
|
|
|
86
92
|
// Store even if the field was not visible to user
|
|
87
93
|
submissionData[pageUrl].formData[condId] = condValue;
|
|
94
|
+
// handle fileInput
|
|
95
|
+
if (condType === "fileInput") {
|
|
96
|
+
// change the name of the key to include "Attachment" at the end but not have the original key
|
|
97
|
+
submissionData[pageUrl].formData[condId + "Attachment"] = condValue;
|
|
98
|
+
delete submissionData[pageUrl].formData[condId];
|
|
99
|
+
}
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
102
|
}
|
|
@@ -291,6 +303,10 @@ function getValue(formElement, pageUrl, req, siteId) {
|
|
|
291
303
|
let value = ""
|
|
292
304
|
if (formElement.element === "dateInput") {
|
|
293
305
|
value = getDateInputISO(pageUrl, formElement.params.name, req, siteId);
|
|
306
|
+
} else if (formElement.element === "fileInput") {
|
|
307
|
+
// unneeded handle of `Attachment` at the end
|
|
308
|
+
// value = dataLayer.getFormDataValue(req.session, siteId, pageUrl, formElement.params.name + "Attachment");
|
|
309
|
+
value = dataLayer.getFormDataValue(req.session, siteId, pageUrl, formElement.params.name);
|
|
294
310
|
} else {
|
|
295
311
|
value = dataLayer.getFormDataValue(req.session, siteId, pageUrl, formElement.params.name);
|
|
296
312
|
}
|
|
@@ -340,6 +356,17 @@ function getValueLabel(formElement, value, pageUrl, req, siteId, service) {
|
|
|
340
356
|
return govcyResources.getSameMultilingualObject(service.site.languages, formattedDate);
|
|
341
357
|
}
|
|
342
358
|
|
|
359
|
+
// handle fileInput
|
|
360
|
+
if (formElement.element === "fileInput") {
|
|
361
|
+
// TODO: Ask Andreas how to handle empty file inputs
|
|
362
|
+
if (value) {
|
|
363
|
+
return govcyResources.staticResources.text.fileUploaded;
|
|
364
|
+
} else {
|
|
365
|
+
return govcyResources.getSameMultilingualObject(service.site.languages, "");
|
|
366
|
+
// return govcyResources.staticResources.text.fileNotUploaded;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
343
370
|
// textInput, textArea, etc.
|
|
344
371
|
return govcyResources.getSameMultilingualObject(service.site.languages, value);
|
|
345
372
|
}
|
|
@@ -263,8 +263,9 @@ export function validateFormElements(elements, formData, pageUrl) {
|
|
|
263
263
|
formData[`${field.params.name}_day`]]
|
|
264
264
|
.filter(Boolean) // Remove empty values
|
|
265
265
|
.join("-") // Join remaining parts
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
// unneeded handle of `Attachment` at the end
|
|
267
|
+
// : (field.element === "fileInput") // Handle fileInput
|
|
268
|
+
// ? formData[`${field.params.name}Attachment`] || ""
|
|
268
269
|
: formData[field.params.name] || ""; // Get submitted value
|
|
269
270
|
|
|
270
271
|
//Autocheck: check for "checkboxes", "radios", "select" if `fieldValue` is one of the `field.params.items
|
|
@@ -313,7 +314,9 @@ export function validateFormElements(elements, formData, pageUrl) {
|
|
|
313
314
|
.filter(Boolean) // Remove empty values
|
|
314
315
|
.join("-") // Join remaining parts
|
|
315
316
|
: (conditionalElement.element === "fileInput") // Handle fileInput
|
|
316
|
-
|
|
317
|
+
// unneeded handle of `Attachment` at the end
|
|
318
|
+
// ? formData[`${conditionalElement.params.name}Attachment`] || ""
|
|
319
|
+
? formData[`${conditionalElement.params.name}`] || ""
|
|
317
320
|
: formData[conditionalElement.params.name] || ""; // Get submitted value
|
|
318
321
|
|
|
319
322
|
//Autocheck: check for "checkboxes", "radios", "select" if `fieldValue` is one of the `field.params.items`
|