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

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.
@@ -17,7 +17,7 @@ import { logger } from "./govcyLogger.mjs";
17
17
  export function prepareSubmissionData(req, siteId, service) {
18
18
  // Get the raw data from the session store
19
19
  // const rawData = dataLayer.getSiteInputData(req.session, siteId);
20
-
20
+
21
21
  // ----- Conditional logic comes here
22
22
  // Filter site input data based on active pages only
23
23
  // const rawData = {};
@@ -32,7 +32,7 @@ export function prepareSubmissionData(req, siteId, service) {
32
32
  // }
33
33
  // }
34
34
 
35
- // ----- consistent data model for submission_data (CONFIG-BASED)
35
+ // ----- consistent data model for submissionData (CONFIG-BASED)
36
36
  const submissionData = {};
37
37
 
38
38
  // Loop through every page in the service definition
@@ -48,7 +48,8 @@ export function prepareSubmissionData(req, siteId, service) {
48
48
 
49
49
  if (!formElement) continue; // ⛔ Skip pages without a <form> element
50
50
 
51
- submissionData[pageUrl] = { formData: {} }; // ✅ Now initialize only if a form is present
51
+ // submissionData[pageUrl] = { formData: {} }; // ✅ Now initialize only if a form is present
52
+ submissionData[pageUrl] = {}; // ✅ Now initialize only if a form is present
52
53
 
53
54
  // Traverse the form elements inside the form
54
55
  for (const element of formElement.params.elements || []) {
@@ -64,13 +65,16 @@ export function prepareSubmissionData(req, siteId, service) {
64
65
  const value = getValue(element, pageUrl, req, siteId) ?? "";
65
66
 
66
67
  // Store in submissionData
67
- submissionData[pageUrl].formData[elId] = value;
68
+ // submissionData[pageUrl].formData[elId] = value;
69
+ submissionData[pageUrl][elId] = value;
68
70
 
69
71
  // handle fileInput
70
72
  if (elType === "fileInput") {
71
73
  // 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
+ // submissionData[pageUrl].formData[elId + "Attachment"] = value;
75
+ submissionData[pageUrl][elId + "Attachment"] = value;
76
+ // delete submissionData[pageUrl].formData[elId];
77
+ delete submissionData[pageUrl][elId];
74
78
  }
75
79
 
76
80
  // 🔄 If radios with conditionalElements, walk ALL options
@@ -90,12 +94,15 @@ export function prepareSubmissionData(req, siteId, service) {
90
94
  const condValue = getValue(condElement, pageUrl, req, siteId) ?? "";
91
95
 
92
96
  // Store even if the field was not visible to user
93
- submissionData[pageUrl].formData[condId] = condValue;
97
+ // submissionData[pageUrl].formData[condId] = condValue;
98
+ submissionData[pageUrl][condId] = condValue;
94
99
  // handle fileInput
95
100
  if (condType === "fileInput") {
96
101
  // 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];
102
+ // submissionData[pageUrl].formData[condId + "Attachment"] = condValue;
103
+ submissionData[pageUrl][condId + "Attachment"] = condValue;
104
+ // delete submissionData[pageUrl].formData[condId];
105
+ delete submissionData[pageUrl][condId];
99
106
  }
100
107
  }
101
108
  }
@@ -103,7 +110,7 @@ export function prepareSubmissionData(req, siteId, service) {
103
110
  }
104
111
  }
105
112
  logger.debug("Submission Data prepared:", submissionData);
106
- // ----- END config-based stable submission_data block
113
+ // ----- END config-based stable submissionData block
107
114
 
108
115
  // Get the print-friendly data from the session store
109
116
  const printFriendlyData = preparePrintFriendlyData(req, siteId, service);
@@ -112,14 +119,14 @@ export function prepareSubmissionData(req, siteId, service) {
112
119
  const reviewSummaryList = generateReviewSummary(printFriendlyData, req, siteId, false);
113
120
  // Prepare the submission data object
114
121
  return {
115
- submission_username: dataLayer.getUser(req.session).name,
116
- submission_email: dataLayer.getUser(req.session).email,
117
- submission_data: submissionData, // Raw data as submitted by the user in each page
118
- submission_data_version: service.site?.submission_data_version || "", // The submission data version
119
- print_friendly_data: printFriendlyData, // Print-friendly data
120
- renderer_data: reviewSummaryList, // Renderer data of the summary list
121
- renderer_version: service.site?.renderer_version || "", // The renderer version
122
- design_systems_version: service.site?.design_systems_version || "", // The design systems version
122
+ submissionUsername: dataLayer.getUser(req.session).name,
123
+ submissionEmail: dataLayer.getUser(req.session).email,
124
+ submissionData: submissionData, // Raw data as submitted by the user in each page
125
+ submissionDataVersion: service.site?.submission_data_version || "", // The submission data version
126
+ printFriendlyData: printFriendlyData, // Print-friendly data
127
+ rendererData: reviewSummaryList, // Renderer data of the summary list
128
+ rendererVersion: service.site?.renderer_version || "", // The renderer version
129
+ designSystemsVersion: service.site?.design_systems_version || "", // The design systems version
123
130
  service: { // Service info
124
131
  id: service.site.id, // Service ID
125
132
  title: service.site.title // Service title multilingual object
@@ -136,16 +143,16 @@ export function prepareSubmissionData(req, siteId, service) {
136
143
  * @returns {object} The API-ready submission data object with all fields as strings
137
144
  */
138
145
  export function prepareSubmissionDataAPI(data) {
139
-
146
+
140
147
  return {
141
- submission_username: String(data.submission_username ?? ""),
142
- submission_email: String(data.submission_email ?? ""),
143
- submission_data: JSON.stringify(data.submission_data ?? {}),
144
- submission_data_version: String(data.submission_data_version ?? ""),
145
- print_friendly_data: JSON.stringify(data.print_friendly_data ?? []),
146
- renderer_data: JSON.stringify(data.renderer_data ?? {}),
147
- renderer_version: String(data.renderer_version ?? ""),
148
- design_systems_version: String(data.design_systems_version ?? ""),
148
+ submissionUsername: String(data.submissionUsername ?? ""),
149
+ submissionEmail: String(data.submissionEmail ?? ""),
150
+ submissionData: JSON.stringify(data.submissionData ?? {}),
151
+ submissionDataVersion: String(data.submissionDataVersion ?? ""),
152
+ printFriendlyData: JSON.stringify(data.printFriendlyData ?? []),
153
+ rendererData: JSON.stringify(data.rendererData ?? {}),
154
+ rendererVersion: String(data.rendererVersion ?? ""),
155
+ designSystemsVersion: String(data.designSystemsVersion ?? ""),
149
156
  service: JSON.stringify(data.service ?? {})
150
157
  };
151
158
  }
@@ -225,7 +232,7 @@ export function preparePrintFriendlyData(req, siteId, service) {
225
232
  }
226
233
  }
227
234
 
228
- return submissionData ;
235
+ return submissionData;
229
236
  }
230
237
 
231
238
  //------------------------------- Helper Functions -------------------------------//
@@ -310,6 +317,19 @@ function getValue(formElement, pageUrl, req, siteId) {
310
317
  } else {
311
318
  value = dataLayer.getFormDataValue(req.session, siteId, pageUrl, formElement.params.name);
312
319
  }
320
+
321
+ // 🔁 Normalize checkboxes: always return an array
322
+ if (formElement.element === "checkboxes") {
323
+ // If no value, return empty array
324
+ if (value == null || value === "") return [];
325
+ // If already an array, return as-is (but strip empties just in case)
326
+ if (Array.isArray(value)) {
327
+ // Strip empties just in case
328
+ return value.filter(v => v != null && v !== "");
329
+ }
330
+ // Else single value, convert to array
331
+ return [String(value)];
332
+ }
313
333
  return value;
314
334
  }
315
335
 
@@ -437,7 +457,34 @@ export function generateReviewSummary(submissionData, req, siteId, showChangeLin
437
457
  };
438
458
  }
439
459
 
440
-
460
+ /**
461
+ * Helper function to create a summary list item for file links.
462
+ * @param {object} key the key of multilingual object
463
+ * @param {string} value the value
464
+ * @param {string} siteId the site id
465
+ * @param {string} pageUrl the page url
466
+ * @param {string} elementName the element name
467
+ * @returns {object} the summary list item with file link
468
+ */
469
+ function createSummaryListItemFileLink(key, value, siteId, pageUrl, elementName) {
470
+ return {
471
+ "key": key,
472
+ "value": [
473
+ {
474
+ "element": "htmlElement",
475
+ "params": {
476
+ "text": {
477
+ "en": `<a href="/${siteId}/${pageUrl}/view-file/${elementName}" target="_blank">${govcyResources.staticResources.text.viewFile.en}<span class="govcy-visually-hidden"> ${key?.en || ""}</span></a>`,
478
+ "el": `<a href="/${siteId}/${pageUrl}/view-file/${elementName}" target="_blank">${govcyResources.staticResources.text.viewFile.el}<span class="govcy-visually-hidden"> ${key?.el || ""}</span></a>`,
479
+ "tr": `<a href="/${siteId}/${pageUrl}/view-file/${elementName}" target="_blank">${govcyResources.staticResources.text.viewFile.tr}<span class="govcy-visually-hidden"> ${key?.tr || ""}</span></a>`
480
+ }
481
+ }
482
+ }
483
+ ]
484
+ };
485
+ }
486
+
487
+
441
488
 
442
489
 
443
490
  // Loop through each page in the submission data
@@ -452,8 +499,14 @@ export function generateReviewSummary(submissionData, req, siteId, showChangeLin
452
499
  for (const field of fields) {
453
500
  const label = field.label;
454
501
  const valueLabel = getSubmissionValueLabelString(field.valueLabel, req.globalLang);
455
- // add the field to the summary entry
456
- summaryListInner.params.items.push(createSummaryListItem(label, valueLabel));
502
+ // --- HACK --- to see if this is a file element
503
+ // check if field.value is an object with `sha256` and `fileId` properties
504
+ if (typeof field.value === "object" && field.value.hasOwnProperty("sha256") && field.value.hasOwnProperty("fileId") && showChangeLinks) {
505
+ summaryListInner.params.items.push(createSummaryListItemFileLink(label, valueLabel, siteId, pageUrl, field.name));
506
+ } else {
507
+ // add the field to the summary entry
508
+ summaryListInner.params.items.push(createSummaryListItem(label, valueLabel));
509
+ }
457
510
  }
458
511
 
459
512
  // Add inner summary list to the main summary list
@@ -510,7 +563,7 @@ export function generateSubmitEmail(service, submissionData, submissionId, req)
510
563
  }
511
564
  );
512
565
  }
513
-
566
+
514
567
  // Add data title to the body
515
568
  body.push(
516
569
  {
@@ -528,7 +581,7 @@ export function generateSubmitEmail(service, submissionData, submissionId, req)
528
581
  body.push(
529
582
  {
530
583
  component: "bodyHeading",
531
- params: {"headingLevel":2},
584
+ params: { "headingLevel": 2 },
532
585
  body: govcyResources.getLocalizeContent(pageTitle, req.globalLang)
533
586
  }
534
587
  );
@@ -538,14 +591,14 @@ export function generateSubmitEmail(service, submissionData, submissionId, req)
538
591
  for (const field of fields) {
539
592
  const label = govcyResources.getLocalizeContent(field.label, req.globalLang);
540
593
  const valueLabel = getSubmissionValueLabelString(field.valueLabel, req.globalLang);
541
- dataUl.push({key: label, value: valueLabel});
594
+ dataUl.push({ key: label, value: valueLabel });
542
595
  }
543
596
  // add data to the body
544
597
  body.push(
545
- {
546
- component: "bodyKeyValue",
547
- params: {type:"ul", items: dataUl},
548
- });
598
+ {
599
+ component: "bodyKeyValue",
600
+ params: { type: "ul", items: dataUl },
601
+ });
549
602
 
550
603
  }
551
604
 
@@ -566,84 +619,84 @@ export function generateSubmitEmail(service, submissionData, submissionId, req)
566
619
 
567
620
 
568
621
 
569
- /*
622
+ /*
570
623
  {
571
- "bank-details": {
572
- "formData": {
573
- "AccountName": "asd",
574
- "Iban": "CY12 0020 0123 0000 0001 2345 6789",
575
- "Swift": "BANKCY2NXXX",
576
- "_csrf": "sjknv79rxjgv0uggo0d5312vzgz37jsh"
577
- }
578
- },
579
- "answer-bank-boc": {
580
- "formData": {
581
- "Objection": "Object",
582
- "country": "Azerbaijan",
583
- "ObjectionReason": "ObjectionReasonCode1",
584
- "ObjectionExplanation": "asdsa",
585
- "DepositsBOCAttachment": "",
586
- "_csrf": "sjknv79rxjgv0uggo0d5312vzgz37jsh"
587
- }
588
- },
589
- "bank-settlement": {
590
- "formData": {
591
- "ReceiveSettlementExplanation": "",
592
- "ReceiveSettlementDate_day": "",
593
- "ReceiveSettlementDate_month": "",
594
- "ReceiveSettlementDate_year": "",
595
- "ReceiveSettlement": "no",
596
- "_csrf": "sjknv79rxjgv0uggo0d5312vzgz37jsh"
597
- }
598
- }
624
+ "bank-details": {
625
+ "formData": {
626
+ "AccountName": "asd",
627
+ "Iban": "CY12 0020 0123 0000 0001 2345 6789",
628
+ "Swift": "BANKCY2NXXX",
629
+ "_csrf": "sjknv79rxjgv0uggo0d5312vzgz37jsh"
630
+ }
631
+ },
632
+ "answer-bank-boc": {
633
+ "formData": {
634
+ "Objection": "Object",
635
+ "country": "Azerbaijan",
636
+ "ObjectionReason": "ObjectionReasonCode1",
637
+ "ObjectionExplanation": "asdsa",
638
+ "DepositsBOCAttachment": "",
639
+ "_csrf": "sjknv79rxjgv0uggo0d5312vzgz37jsh"
640
+ }
641
+ },
642
+ "bank-settlement": {
643
+ "formData": {
644
+ "ReceiveSettlementExplanation": "",
645
+ "ReceiveSettlementDate_day": "",
646
+ "ReceiveSettlementDate_month": "",
647
+ "ReceiveSettlementDate_year": "",
648
+ "ReceiveSettlement": "no",
649
+ "_csrf": "sjknv79rxjgv0uggo0d5312vzgz37jsh"
650
+ }
651
+ }
599
652
  }
600
653
 
601
654
 
602
655
 
603
656
  [
604
- {
605
- pageUrl: "personal-details",
606
- pageTitle: { en: "Personal data", el: "Προσωπικά στοιχεία" }, // from pageData.title in correct language
607
- fields: [
608
- [
609
- {
610
- id: "firstName",
611
- label: { en: "First Name", el: "Όνομα" },
612
- value: "John", // The actual user input value
613
- valueLabel: { en: "John", el: "John" } // Same label as the value for text inputs
614
- },
615
- {
616
- id: "lastName",
617
- label: { en: "Last Name", el: "Επίθετο" },
618
- value: "Doe", // The actual user input value
619
- valueLabel: { en: "Doe", el: "Doe" } // Same label as the value for text inputs
620
- },
621
- {
622
- id: "gender",
623
- label: { en: "Gender", el: "Φύλο" },
624
- value: "m", // The actual value ("male")
625
- valueLabel: { en: "Male", el: "Άντρας" } // The corresponding label for "male"
626
- },
627
- {
628
- id: "languages",
629
- label: { en: "Languages", el: "Γλώσσες" },
630
- value: ["en", "el"], // The selected values ["en", "el"]
631
- valueLabel: [
632
- { en: "English", el: "Αγγλικά" }, // Labels corresponding to "en" and "el"
633
- { en: "Greek", el: "Ελληνικά" }
634
- ]
635
- },
636
- {
637
- id: "birthDate",
638
- label: { en: "Birth Date", el: "Ημερομηνία Γέννησης" },
639
- value: "1990-01-13", // The actual value based on user input
640
- valueLabel: "13/1/1990" // Date inputs label will be conveted to D/M/YYYY
641
- }
642
- ]
643
- },
644
- ...
657
+ {
658
+ pageUrl: "personal-details",
659
+ pageTitle: { en: "Personal data", el: "Προσωπικά στοιχεία" }, // from pageData.title in correct language
660
+ fields: [
661
+ [
662
+ {
663
+ id: "firstName",
664
+ label: { en: "First Name", el: "Όνομα" },
665
+ value: "John", // The actual user input value
666
+ valueLabel: { en: "John", el: "John" } // Same label as the value for text inputs
667
+ },
668
+ {
669
+ id: "lastName",
670
+ label: { en: "Last Name", el: "Επίθετο" },
671
+ value: "Doe", // The actual user input value
672
+ valueLabel: { en: "Doe", el: "Doe" } // Same label as the value for text inputs
673
+ },
674
+ {
675
+ id: "gender",
676
+ label: { en: "Gender", el: "Φύλο" },
677
+ value: "m", // The actual value ("male")
678
+ valueLabel: { en: "Male", el: "Άντρας" } // The corresponding label for "male"
679
+ },
680
+ {
681
+ id: "languages",
682
+ label: { en: "Languages", el: "Γλώσσες" },
683
+ value: ["en", "el"], // The selected values ["en", "el"]
684
+ valueLabel: [
685
+ { en: "English", el: "Αγγλικά" }, // Labels corresponding to "en" and "el"
686
+ { en: "Greek", el: "Ελληνικά" }
687
+ ]
688
+ },
689
+ {
690
+ id: "birthDate",
691
+ label: { en: "Birth Date", el: "Ημερομηνία Γέννησης" },
692
+ value: "1990-01-13", // The actual value based on user input
693
+ valueLabel: "13/1/1990" // Date inputs label will be conveted to D/M/YYYY
694
+ }
695
+ ]
696
+ },
697
+ ...
645
698
  ]
646
699
 
647
700
 
648
701
 
649
- */
702
+ */