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

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 (32) hide show
  1. package/README.md +1051 -83
  2. package/package.json +10 -3
  3. package/src/auth/cyLoginAuth.mjs +2 -1
  4. package/src/index.mjs +20 -1
  5. package/src/middleware/cyLoginAuth.mjs +11 -1
  6. package/src/middleware/govcyCsrf.mjs +15 -1
  7. package/src/middleware/govcyFileDeleteHandler.mjs +320 -0
  8. package/src/middleware/govcyFileUpload.mjs +36 -0
  9. package/src/middleware/govcyFileViewHandler.mjs +161 -0
  10. package/src/middleware/govcyFormsPostHandler.mjs +1 -1
  11. package/src/middleware/govcyHttpErrorHandler.mjs +4 -3
  12. package/src/middleware/govcyPDFRender.mjs +3 -1
  13. package/src/middleware/govcyPageHandler.mjs +3 -3
  14. package/src/middleware/govcyPageRender.mjs +10 -0
  15. package/src/middleware/govcyReviewPageHandler.mjs +4 -1
  16. package/src/middleware/govcyReviewPostHandler.mjs +1 -1
  17. package/src/middleware/govcySuccessPageHandler.mjs +2 -3
  18. package/src/public/js/govcyFiles.js +299 -0
  19. package/src/public/js/govcyForms.js +19 -8
  20. package/src/resources/govcyResources.mjs +85 -4
  21. package/src/utils/govcyApiDetection.mjs +17 -0
  22. package/src/utils/govcyApiRequest.mjs +30 -5
  23. package/src/utils/govcyApiResponse.mjs +31 -0
  24. package/src/utils/govcyConstants.mjs +5 -1
  25. package/src/utils/govcyDataLayer.mjs +211 -11
  26. package/src/utils/govcyExpressions.mjs +1 -1
  27. package/src/utils/govcyFormHandling.mjs +81 -5
  28. package/src/utils/govcyHandleFiles.mjs +307 -0
  29. package/src/utils/govcyLoadSubmissionDataAPIs.mjs +10 -3
  30. package/src/utils/govcySubmitData.mjs +186 -106
  31. package/src/utils/govcyTempSave.mjs +2 -1
  32. package/src/utils/govcyValidator.mjs +7 -0
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  ![License](https://img.shields.io/github/license/gov-cy/govcy-express-services)
4
4
  [![Unit test](https://github.com/gov-cy/govcy-express-services/actions/workflows/unit-test.yml/badge.svg)](https://github.com/gov-cy/govcy-express-services/actions/workflows/unit-test.yml)
5
5
  [![tag-and-publish-on-version-change](https://github.com/gov-cy/govcy-express-services/actions/workflows/tag-and-publish-on-version-change.yml/badge.svg)](https://github.com/gov-cy/govcy-express-services/actions/workflows/tag-and-publish-on-version-change.yml)
6
+ ![coverage](coverage-badges.svg)
6
7
 
7
8
  > ⚠️ **Warning:**
8
9
  > This package is **under active development** and is not a finished product. It is intended for testing, acceptance, integration, and browser testing purposes only.
@@ -12,10 +13,12 @@
12
13
  > You are responsible for ensuring your own compliance, security, and quality assurance processes.
13
14
 
14
15
  ## 📝 Description
15
- This project is an Express-based project that dynamically renders online service forms using `@gov-cy/govcy-frontend-renderer`. It is designed for developers building government services in Cyprus, enabling them to manage user authentication, form submissions, and OpenID authentication workflows in a timely manner.
16
+ This project is an Express-based project that dynamically renders online service forms using `@gov-cy/govcy-frontend-renderer`, handles data input, validations, renders a review page and submits the data via a submission API. It is designed for developers building government services in Cyprus, enabling them to manage user authentication, form submissions, and OpenID authentication workflows in a timely manner.
16
17
 
17
18
  The project is designed to support the [Linear structure](https://gov-cy.github.io/govcy-design-system-docs/patterns/service_structure/#variant-1---linear-structure) as described in the [Unified Design System](https://gov-cy.github.io/govcy-design-system-docs/).
18
19
 
20
+ The APIs used for submission, temporary save and file uploads are not part of this project. The project has been designed to work together with the **DSF Submission plarform** and all API calls are based on the **DSF Submission Platform APIs**. This readme file describes the definition of these APIs if you wish to develop your own for your own government back-end solution. For more details about the DSF Submission Platform [contact the DSF team](https://dsf.dmrid.gov.cy/contact/).
21
+
19
22
  ![govcy-express-services](express-services.png)
20
23
 
21
24
  ## Table of contents
@@ -27,14 +30,15 @@ The project is designed to support the [Linear structure](https://gov-cy.github.
27
30
  - [✅ Best Practices](#-best-practices)
28
31
  - [📦 Full installation guide](#-full-installation-guide)
29
32
  - [🛠️ Usage](#%EF%B8%8F-usage)
30
- - [🧩 Dynamic services rendering](#-dynamic-services-rendering)
33
+ - [🧩 Dynamic services](#-dynamic-services)
31
34
  - [🛡️ Site eligibility checks](#%EF%B8%8F-site-eligibility-checks)
32
35
  - [📤 Site submissions](#-site-submissions)
33
36
  - [✅ Input validations](#-input-validations)
34
- - [ Conditional logic](#-conditional-logic)
35
- - [💾 Temporary save](#-temporary-save)
37
+ - [🔀 Conditional logic](#-conditional-logic)
38
+ - [💾 Temporary save feature](#-temporary-save-feature)
39
+ - [🗃️ Files uploads feature](#%EF%B8%8F-files-uploads-feature)
36
40
  - [🛣️ Routes](#%EF%B8%8F-routes)
37
- - [👨‍💻 Enviromental variables](#-enviromental-variables)
41
+ - [👨‍💻 Environment variables](#-environment-variables)
38
42
  - [🔒 Security note](#-security-note)
39
43
  - [❓ Troubleshooting / FAQ](#-troubleshooting--faq)
40
44
  - [🙏 Credits](#-credits)
@@ -44,7 +48,7 @@ The project is designed to support the [Linear structure](https://gov-cy.github.
44
48
 
45
49
  ## ✨ Features
46
50
  - Dynamic form rendering from JSON templates
47
- - Support for `textInput`, `textArea`, `select`, `radios`, `checkboxes`, `datePicker`, `dateInput`
51
+ - Support for `textInput`, `textArea`, `select`, `radios`, `checkboxes`, `datePicker`, `dateInput`, `fileInput` elements
48
52
  - Support for `conditional radios`
49
53
  - Dynamic creation of check your answers page
50
54
  - OpenID Connect authentication with CY Login
@@ -57,6 +61,7 @@ The project is designed to support the [Linear structure](https://gov-cy.github.
57
61
  - Site level API eligibility checks
58
62
  - API integration with retry logic for form submissions.
59
63
  - Optional temporary save of in-progress form data via configurable API endpoints
64
+ - Optional file uploads via API endpoints
60
65
 
61
66
  ## 📋 Prerequisites
62
67
  - Node.js 20+
@@ -137,9 +142,555 @@ The CY Login tokens are used to also connect with the various APIs through [cyCo
137
142
 
138
143
  The CY Login settings are configured in the `secrets/.env` file.
139
144
 
140
- ### 🧩 Dynamic Services Rendering
145
+ ### 🧩 Dynamic Services
141
146
  Services are rendered dynamically using JSON templates stored in the `/data` folder. All the service configuration, pages, routes, and logic is stored in the JSON files. The service will load `data/:siteId.json` to get the form data when a user visits `/:siteId/:pageUrl`. Checkout the [express-service-shema.json](express-service-shema.json) and the example JSON structure of the **[test.json](data/test.json)** file for more details.
142
147
 
148
+ Here is an example JSON config:
149
+
150
+ ```json
151
+ {
152
+ "site": {
153
+ "id": "test",
154
+ "lang": "el", //<-- Default language
155
+ "languages": [ //<-- Supported languages
156
+ {
157
+ "code": "el",
158
+ "label": "EL",
159
+ "alt": "Ελληνική γλώσσα",
160
+ "href": "?lang=el"
161
+ },
162
+ {
163
+ "code": "en",
164
+ "label": "EN",
165
+ "alt": "English language",
166
+ "href": "?lang=en"
167
+ }
168
+ ],
169
+ "footerLinks": [ //<-- Links on the footer
170
+ {
171
+ "label": {
172
+ "el": "Δήλωση απορρήτου",
173
+ "en": "Privacy statement",
174
+ "tr": "Privacy statement"
175
+ },
176
+ "href": "test/privacy-statement"
177
+ },
178
+ {
179
+ "label": {
180
+ "el": "Cookies",
181
+ "en": "Cookies",
182
+ "tr": "Cookies"
183
+ },
184
+ "href": "test/cookie-policy"
185
+ },
186
+ {
187
+ "label": {
188
+ "el": "Προσβασιμότητα",
189
+ "en": "Accessibility",
190
+ "tr": "Accessibility"
191
+ },
192
+ "href": "test/accessibility-statement"
193
+ }
194
+ ],
195
+ "footerIcons": [ //<-- Icons on the footer
196
+ {
197
+ "target": "_blank",
198
+ "src": {
199
+ "el": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/FundedbyEU_NextGeneration_H53-EL.png",
200
+ "en": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/FundedbyEU_NextGeneration_H53-EN.png",
201
+ "tr": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/FundedbyEU_NextGeneration_H53-EN.png"
202
+ },
203
+ "alt": {
204
+ "el": "Χρηματοδοτείται από την ΕΕ Next Generation EU",
205
+ "en": "Funded by the EU Next Generation EU",
206
+ "tr": "Funded by the EU Next Generation EU"
207
+ },
208
+ "href": {
209
+ "el": "https://europa.eu/",
210
+ "en": "https://europa.eu/",
211
+ "tr": "https://europa.eu/"
212
+ },
213
+ "title": {
214
+ "el": "Μετάβαση στην ιστοσελίδα της ΕΕ",
215
+ "en": "Go to EU website",
216
+ "tr": "Go to EU website"
217
+ }
218
+ },
219
+ {
220
+ "target": "_blank",
221
+ "src": {
222
+ "el": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/CYpros%20to%20aurio%20logo%20eng_H53_EL.png",
223
+ "en": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/CYpros%20to%20aurio%20logo%20eng_H53_EN.png",
224
+ "tr": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/CYpros%20to%20aurio%20logo%20eng_H53_EN.png"
225
+ },
226
+ "alt": {
227
+ "el": "Κύπρος το Αύριο, σχέδιο ανάκαμψης και ανθεντικότητας",
228
+ "en": "Cyprus tomorrow, recovery and resilience plan",
229
+ "tr": "Cyprus tomorrow, recovery and resilience plan"
230
+ },
231
+ "href": {
232
+ "el": "http://www.cyprus-tomorrow.gov.cy/",
233
+ "en": "http://www.cyprus-tomorrow.gov.cy/",
234
+ "tr": "http://www.cyprus-tomorrow.gov.cy/"
235
+ },
236
+ "title": {
237
+ "el": "Μετάβαση στην ιστοσελίδα Κύπρος το Αύριο",
238
+ "en": "Go to Cyprus Tomorrow website",
239
+ "tr": "Go to Cyprus Tomorrow website"
240
+ }
241
+ }
242
+ ],
243
+ "menu": { //<-- Menu altext
244
+ "el": "Μενού",
245
+ "en": "Menu",
246
+ "tr": "Menu"
247
+ },
248
+ "title": { //<-- Service title (meta)
249
+ "el": "Υπηρεσία τεστ",
250
+ "en": "Test service",
251
+ "tr": ""
252
+ },
253
+ "headerTitle": { // <-- The header title settings
254
+ "title": { //<-- Service title (as it apears in the header)
255
+ "el": "[Το ΟΝΟΜΑ της υπηρεσίας που θα φαίνεται στις φόρμες]",
256
+ "en": "[The NAME of the service as it will appear on forms]",
257
+ "tr": ""
258
+ },
259
+ "href": { // <-- The relative URL of the header title link (for each language)
260
+ "el":"/service-id",
261
+ "en":"/service-id",
262
+ "tr":"/service-id"
263
+ }
264
+ },
265
+ "description": { //<-- Service description (meta)
266
+ "el": "[Υποβάλετε αίτηση για ...]",
267
+ "en": "[Submit an application ...]",
268
+ "tr": ""
269
+ },
270
+ "url": "https://gov.cy", //<-- URL in (meta, for example `og:url`)
271
+ "cdn": { //<-- CDN URL and integrity
272
+ "dist": "https://cdn.jsdelivr.net/gh/gov-cy/govcy-design-system@3.2.0/dist",
273
+ "cssIntegrity": "sha384-qjx16YXHG+Vq/NVtwU2aDTc7DoLOyaVNuOHrwA3aTrckpM/ycxZoR5dx7ezNJ/Lv",
274
+ "jsIntegrity": "sha384-tqEyCdi3GS4uDXctplAd7ODjiK5fo2Xlqv65e8w/cVvrcBf89tsxXFHXXNiUDyM7"
275
+ },
276
+ "submission_data_version": "1", //<-- Submission data version
277
+ "renderer_version": "1.16.1", //<-- govcy-frontend-renderer version
278
+ "design_systems_version": "3.2.0", //<-- govcy-design-system version
279
+ "homeRedirectPage": { //<-- Home redirect page
280
+ "el": "https://www.gov.cy/service/aitisi-gia-taftotita/",
281
+ "en": "https://www.gov.cy/en/service/issue-an-id-card/",
282
+ "tr": "https://www.gov.cy/en/service/issue-an-id-card/"
283
+ },
284
+ "copyrightText": { //<-- Copyright text
285
+ "el": "Κυπριακή Δημοκρατία, 2025",
286
+ "en": "Republic of Cyprus, 2025",
287
+ "tr": "Republic of Cyprus, 2025"
288
+ },
289
+ "submissionAPIEndpoint": { //<-- Submission API endpoint
290
+ "url": "TEST_SUBMISSION_API_URL",
291
+ "method": "POST",
292
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
293
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
294
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY",
295
+ "response": {
296
+ "errorResponse": {
297
+ "102": {
298
+ "error": "user not administrator",
299
+ "page": "/test/user-not-admin"
300
+ },
301
+ "105": {
302
+ "error": "user not registration",
303
+ "page": "/test/user-not-registered"
304
+ }
305
+ }
306
+ }
307
+ },
308
+ "submissionGetAPIEndpoint": { //<-- Submission GET API endpoint for temporary saving
309
+ "url": "TEST_SUBMISSION_GET_API_URL",
310
+ "method": "GET",
311
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
312
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
313
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
314
+ },
315
+ "submissionPutAPIEndpoint": { //<-- Submission PUT API endpoint for temporary saving
316
+ "url": "TEST_SUBMISSION_PUT_API_URL",
317
+ "method": "PUT",
318
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
319
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
320
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
321
+ },
322
+ "fileUploadAPIEndpoint": { //<-- File upload API endpoint
323
+ "url": "TEST_UPLOAD_FILE_API_URL",
324
+ "method": "POST",
325
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
326
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
327
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
328
+ },
329
+ "fileDownloadAPIEndpoint": { //<-- File download API endpoint
330
+ "url": "TEST_DOWNLOAD_FILE_API_URL",
331
+ "method": "GET",
332
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
333
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
334
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
335
+ },
336
+ "fileDeleteAPIEndpoint": { //<-- File delete API endpoint
337
+ "url": "TEST_DELETE_FILE_API_URL",
338
+ "method": "DELETE",
339
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
340
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
341
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
342
+ },
343
+ "eligibilityAPIEndpoints": [ //<-- Eligibility API endpoints
344
+ {
345
+ "url": "TEST_ELIGIBILITY_2_API_URL",
346
+ "method": "GET",
347
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
348
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
349
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY",
350
+ "cashingTimeoutMinutes": "60",
351
+ "params": {},
352
+ "response": {
353
+ "errorResponse": {
354
+ "105": {
355
+ "error": "user not registration",
356
+ "page": "/test/user-not-registered"
357
+ }
358
+ }
359
+ }
360
+ }
361
+ ]
362
+ },
363
+ "pages": [ //<-- Pages
364
+ {
365
+ "pageData": { //<-- 1st Page's data (form)
366
+ "url": "index", // Page URL
367
+ "title": { // Page title
368
+ "el": "Επιλογή Εγγάφου",
369
+ "en": "Document selection",
370
+ "tr": ""
371
+ },
372
+ "layout": "layouts/govcyBase.njk", // Page layout
373
+ "mainLayout": "two-third", // Page main layout
374
+ "nextPage": "data-entry-radios" // The next page's URL
375
+ },
376
+ "pageTemplate": { //<-- Page template
377
+ "sections": [ //<-- Page sections
378
+ {
379
+ "name": "main", //<-- Main section
380
+ "elements": [ //<-- Main section elements
381
+ {
382
+ "element": "form", // Form element
383
+ "params": {
384
+ "elements": [ // Elements inside the form
385
+ {
386
+ "element": "checkboxes", // Checkboxes element
387
+ "params": { // Checkboxes parameters
388
+ "id": "certificate_select",
389
+ "name": "certificate_select",
390
+ "legend": {
391
+ "el": "Τι έγγραφα επιθυμείτε να εκδώσετε;",
392
+ "en": "What documents do you wish to issue?"
393
+ },
394
+ "items": [
395
+ {
396
+ "value": "birth",
397
+ "text": {
398
+ "el": "Πιστοποιητικό γέννησης​",
399
+ "en": "Birth certificate",
400
+ "tr": ""
401
+ },
402
+ "hint": {
403
+ "el": "Αν η γέννηση έγινε στην Κύπρο ή στο εξωτερικό και έχει ενημερωθεί το μητρώο του Αρχείου Πληθυσμού ",
404
+ "en": "For a birth in Cyprus or abroad which Civil Registry is updated with "
405
+ }
406
+ },
407
+ {
408
+ "value": "permanent_residence",
409
+ "text": {
410
+ "el": "Βεβαίωση μόνιμης διαμονής​",
411
+ "en": "Certificate of permanent residence",
412
+ "tr": ""
413
+ },
414
+ "hint": {
415
+ "el": "Για όσους είναι εγγεγραμμένοι στον εκλογικό κατάλογο",
416
+ "en": "For those registered in the electoral list"
417
+ }
418
+ },
419
+ {
420
+ "value": "student_proof_of_origin",
421
+ "text": {
422
+ "el": "Βεβαίωση καταγωγής",
423
+ "en": "Certificate of origin",
424
+ "tr": ""
425
+ },
426
+ "hint": {
427
+ "el": "Για αίτηση σε πανεπιστήμια στην Ελλάδα",
428
+ "en": "To apply to a university in Greece"
429
+ }
430
+ }
431
+ ],
432
+ "isPageHeading": true,
433
+ "hint": {
434
+ "el": "Επιλέξτε ένα ή περισσότερα έγγραφα",
435
+ "en": "Select one or more documents",
436
+ "tr": ""
437
+ }
438
+ },
439
+ "validations": [ // Checkboxes validations
440
+ {
441
+ "check": "required",
442
+ "params": {
443
+ "checkValue": "",
444
+ "message": {
445
+ "el": "Επιλέξετε ένα ή περισσότερα έγγραφα",
446
+ "en": "Select one or more documents",
447
+ "tr": ""
448
+ }
449
+ }
450
+ }
451
+ ]
452
+ },
453
+ {
454
+ "element": "button",
455
+ "params": {
456
+ "id": "continue",
457
+ "variant": "primary",
458
+ "text": {
459
+ "el": "Συνέχεια",
460
+ "en": "Continue"
461
+ }
462
+ }
463
+ }
464
+ ]
465
+ }
466
+ }
467
+ ]
468
+ }
469
+ ]
470
+ }
471
+ },
472
+ {
473
+ "pageData": { //<-- 2nd Page's data (form)
474
+ "url": "data-entry-radios",
475
+ "title": {
476
+ "el": "Στοιχεία επικοινωνίας ",
477
+ "en": "Contact details",
478
+ "tr": ""
479
+ },
480
+ "layout": "layouts/govcyBase.njk",
481
+ "mainLayout": "two-third",
482
+ "nextPage": "review"
483
+ },
484
+ "pageTemplate": {
485
+ "sections": [
486
+ {
487
+ "name": "beforeMain",
488
+ "elements": [
489
+ {
490
+ "element": "backLink",
491
+ "params": {}
492
+ }
493
+ ]
494
+ },
495
+ {
496
+ "name": "main",
497
+ "elements": [
498
+ {
499
+ "element": "form",
500
+ "params": {
501
+ "elements": [
502
+ {
503
+ "element": "radios",
504
+ "params": {
505
+ "id": "mobile_select",
506
+ "name": "mobile_select",
507
+ "legend": {
508
+ "el": "Σε ποιο κινητό μπορούμε να επικοινωνήσουμε μαζί σας;",
509
+ "en": "What mobile number can we use to contact you?"
510
+ },
511
+ "items": [
512
+ {
513
+ "value": "mobile",
514
+ "text": {
515
+ "el": "Στο [99 123456]",
516
+ "en": "You can use [99 123456]",
517
+ "tr": ""
518
+ }
519
+ },
520
+ {
521
+ "value": "other",
522
+ "text": {
523
+ "el": "Θα δώσω άλλο αριθμό",
524
+ "en": "I will give a different number",
525
+ "tr": ""
526
+ },
527
+ "conditionalElements": [
528
+ {
529
+ "element": "fileInput",
530
+ "params": {
531
+ "id": "proof",
532
+ "name": "proof",
533
+ "label": {
534
+ "el": "Αποδεικτικό τηλεφώνου",
535
+ "en": "Telephone proof",
536
+ "tr": ""
537
+ },
538
+ "isPageHeading": false,
539
+ "hint": {
540
+ "el": "PDF, JPG, JPEG, PNG, είναι οι αποδεκτές μορφές",
541
+ "en": "PDF, JPG, JPEG, PNG are the acceptable formats",
542
+ "tr": ""
543
+ }
544
+ },
545
+ "validations": [
546
+ {
547
+ "check": "required",
548
+ "params": {
549
+ "checkValue": "",
550
+ "message": {
551
+ "el": "Ανεβάστε τον αποδεικτικό τηλεφώνου",
552
+ "en": "Upload the telephone proof",
553
+ "tr": ""
554
+ }
555
+ }
556
+ }
557
+ ]
558
+ }
559
+ ]
560
+ }
561
+ ],
562
+ "isPageHeading": true
563
+ },
564
+ "validations": [
565
+ {
566
+ "check": "required",
567
+ "params": {
568
+ "checkValue": "",
569
+ "message": {
570
+ "el": "Επιλέξετε αν θέλετε να χρησιμοποιήσετε το τηλέφωνο που φαίνεται εδώ, ή κάποιο άλλο",
571
+ "en": "Choose if you'd like to use the phone number shown here, or a different one",
572
+ "tr": ""
573
+ }
574
+ }
575
+ }
576
+ ]
577
+ },
578
+ {
579
+ "element": "button",
580
+ "params": {
581
+ "id": "continue",
582
+ "variant": "primary",
583
+ "text": {
584
+ "el": "Συνέχεια",
585
+ "en": "Continue"
586
+ }
587
+ }
588
+ }
589
+ ]
590
+ }
591
+ }
592
+ ]
593
+ }
594
+ ]
595
+ }
596
+ },
597
+ {
598
+ "pageData": { //<-- 3rd Page's data (not a form)
599
+ "url": "user-not-registered",
600
+ "title": {
601
+ "el": "Δεν είστε εγγεγραμμένοι ",
602
+ "en": "You are not an registered",
603
+ "tr": ""
604
+ },
605
+ "layout": "layouts/govcyBase.njk",
606
+ "mainLayout": "two-third"
607
+ },
608
+ "pageTemplate": {
609
+ "sections": [
610
+ {
611
+ "name": "beforeMain",
612
+ "elements": []
613
+ },
614
+ {
615
+ "name": "main",
616
+ "elements": [
617
+ {
618
+ "element": "textElement",
619
+ "params": {
620
+ "id": "title",
621
+ "type": "h1",
622
+ "text": {
623
+ "el": "Δεν είστε εγγεγραμμένοι",
624
+ "en": "You are not registered"
625
+ }
626
+ }
627
+ },
628
+ {
629
+ "element": "htmlElement",
630
+ "params": {
631
+ "id": "body",
632
+ "text": {
633
+ "el": "<p>Για να υποβάλετε σε υπηρεσία αυτή, χρειάζεται να είστε εγγεγραμμένοι στο ΧΥΖ.</p>",
634
+ "en": "<p>To submit in this service you need to be registered at XYZ.</p>"
635
+ }
636
+ }
637
+ }
638
+ ]
639
+ }
640
+ ]
641
+ }
642
+ },
643
+ {
644
+ "pageData": { //<-- 4th Page's data (not a form)
645
+ "url": "user-not-admin",
646
+ "title": {
647
+ "el": "Δεν είστε διαχειριστής ",
648
+ "en": "You are not an administrator",
649
+ "tr": ""
650
+ },
651
+ "layout": "layouts/govcyBase.njk",
652
+ "mainLayout": "two-third"
653
+ },
654
+ "pageTemplate": {
655
+ "sections": [
656
+ {
657
+ "name": "beforeMain",
658
+ "elements": []
659
+ },
660
+ {
661
+ "name": "main",
662
+ "elements": [
663
+ {
664
+ "element": "textElement",
665
+ "params": {
666
+ "id": "title",
667
+ "type": "h1",
668
+ "text": {
669
+ "el": "Δεν είστε διαχειριστής ",
670
+ "en": "You are not an administrator"
671
+ }
672
+ }
673
+ },
674
+ {
675
+ "element": "htmlElement",
676
+ "params": {
677
+ "id": "body",
678
+ "text": {
679
+ "el": "<p>Για να υποβάλετε σε υπηρεσία αυτή, χρειάζεται να είστε διαχειριστής στο ΧΥΖ.</p>",
680
+ "en": "<p>To submit in this service you need to be an administrator of XYZ.</p>"
681
+ }
682
+ }
683
+ }
684
+ ]
685
+ }
686
+ ]
687
+ }
688
+ }
689
+ ]
690
+ }
691
+
692
+ ```
693
+
143
694
  Here are some details explaining the JSON structure:
144
695
 
145
696
  - `site` object: Contains information about the site, including the site ID, language, and footer links. See [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/tree/main#site-and-page-meta-data-explained) for more details. Some fields that are only specific to the govcy-express-forms project are the following:
@@ -155,8 +706,14 @@ Here are some details explaining the JSON structure:
155
706
  ```
156
707
  - `eligibilityAPIEndpoints` : An array of API endpoints, to be used for service eligibility. See more on the [Eligibility API Endoints](#%EF%B8%8F-site-eligibility-checks) section below.
157
708
  - `submissionAPIEndpoint`: The submission API endpoint, to be used for submitting the form. See more on the [Submission API Endoint](#-site-submissions) section below.
709
+ - `submissionGetAPIEndpoint`: The submission get API endpoint, to be used for getting the submission data. See more on the [temporary save feature](#-temporary-save-feature) section below.
710
+ - `submissionPutAPIEndpoint`: The submission put API endpoint, to be used for temporary saving the submission data. See more on the [temporary save feature](#-temporary-save-feature) section below.
711
+ - `fileUploadAPIEndpoint`: The file upload API endpoint, to be used for uploading files. See more on the [file upload feature](#%EF%B8%8F-files-uploads-feature) section below.
712
+ - `fileDownloadAPIEndpoint`: The file download API endpoint, to be used for downloading files. See more on the [file upload feature](#%EF%B8%8F-files-uploads-feature) section below.
713
+ - `fileDeleteAPIEndpoint`: The file delete API endpoint, to be used for deleting files. See more on the [file upload feature](#%EF%B8%8F-files-uploads-feature) section below.
158
714
  - `pages` array: An array of page objects, each representing a page in the site.
159
715
  - `pageData` object: Contains the metadata to be rendered on the page. See [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/tree/main#site-and-page-meta-data-explained) for more details
716
+ - `nextPage`: The URL of the next page to be rendered after the user clicks the `continue` button.
160
717
  - `pageTemplate` object: Contains the page template to be rendered on the page. See [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/tree/main#json-input-template) for more details
161
718
  - `elements` array: An array of elements to be rendered on the page. See all supported [govcy-frontend-renderer elements](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md) for more details
162
719
 
@@ -211,8 +768,7 @@ Here's an example of a page defined in the JSON file:
211
768
  "element": "backLink",
212
769
  "params": {}
213
770
  }
214
- ],
215
- "params": {}
771
+ ]
216
772
  },
217
773
  {
218
774
  "name": "main",
@@ -297,14 +853,24 @@ Lets break down the JSON config for this page:
297
853
  - `pageData.nextPage` is the next page to redirect to when the user clicks the `continue` button and all validations pass, in this case it will redirect to `/:siteId/telephone-number`
298
854
  - `pageData.conditions` is the array that defines the [conditional logic](#-conditional-logic)
299
855
  - **pageTemplate** is the page's template, which is a JSON object that contains the sections and elements of the page. Check out the [govcy-frontend-renderer's documentation](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/README.md) for more details.
856
+ - `sections` is an array of sections, which is an array of elements. Sections allowed: `beforeMain`, `main`, `afterMain`.
857
+ - `elements` is an array of elements for the said section. Seem more details on the [govcy-frontend-renderer's design elements documentation](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md).
300
858
 
301
859
  **Forms vs static content**
302
860
 
303
861
  - If the `pageTemplate` includes a `form` element in the `main` section and `button` element, the system will treat it as form and will:
304
862
  - Perform the eligibility checks
305
863
  - Display the form
306
- - Collect the form data
307
- - Validate the form data
864
+ - Collect the form data for the following input elements (more details on the [govcy-frontend-renderer's design elements documentation](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md)):
865
+ - `textInput`
866
+ - `textArea`
867
+ - `select`
868
+ - `radios`
869
+ - `checkboxes`
870
+ - `datePicker`
871
+ - `dateInput`
872
+ - `fileInput`: the file upload feature must be enabled to use this element (see more on the [file upload feature](#%EF%B8%8F-files-uploads-feature) section below)
873
+ - Validate the form data (see more on the [Input validations](#-input-validations) section below)
308
874
  - Store the form data in the systems data layer
309
875
  - Redirect the user to the next page (or `review` page if the user came from the review page)
310
876
  - Else if the `pageTemplate` does not include a `form` element in the `main` section, the system will treat it as static content and will:
@@ -494,7 +1060,9 @@ Content-Type: application/json
494
1060
  The API is expected to return a JSON response with the following structure (see [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs) for normalization):
495
1061
 
496
1062
  **On Success:**
497
- ```json
1063
+ ```http
1064
+ HTTP/1.1 200 OK
1065
+
498
1066
  {
499
1067
  "Succeeded": true,
500
1068
  "ErrorCode": 0,
@@ -503,7 +1071,9 @@ The API is expected to return a JSON response with the following structure (see
503
1071
  ```
504
1072
 
505
1073
  **On Failure:**
506
- ```json
1074
+ ```http
1075
+ HTTP/1.1 200 OK
1076
+
507
1077
  {
508
1078
  "Succeeded": false,
509
1079
  "ErrorCode": 102,
@@ -620,14 +1190,14 @@ Accept: text/plain
620
1190
  Content-Type: application/json
621
1191
 
622
1192
  {
623
- "submission_username": "username",
624
- "submission_email": "email@example.com",
625
- "submission_data": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}}}",
626
- "submission_data_version": "1",
627
- "print_friendly_data": "[{\"pageUrl\":\"index\",\"pageTitle\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"fields\":[{\"id\":\"certificate_select\",\"name\":\"certificate_select\",\"label\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[\"birth\",\"permanent_residence\"],\"valueLabel\":[{\"el\":\"Πιστοποιητικό γέννησης​\",\"en\":\"Birth certificate\",\"tr\":\"\"},{\"el\":\"Βεβαίωση μόνιμης διαμονής​\",\"en\":\"Certificate of permanent residence\",\"tr\":\"\"}]}]}]",
628
- "renderer_data": "{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"value\":[{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[{\"element\":\"textElement\",\"params\":{\"text\":{\"en\":\"Birth certificate, Certificate of permanent residence\",\"el\":\"Birth certificate, Certificate of permanent residence\",\"tr\":\"Birth certificate, Certificate of permanent residence\"},\"type\":\"span\"}}]}]}}]}]}}",
629
- "renderer_version": "1.14.3",
630
- "design_systems_version": "3.2.0",
1193
+ "submissionUsername": "username",
1194
+ "submissionEmail": "email@example.com",
1195
+ "submissionData": "{\"index\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}}",
1196
+ "submissionDataVersion": "1",
1197
+ "printFriendlyData": "[{\"pageUrl\":\"index\",\"pageTitle\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"fields\":[{\"id\":\"certificate_select\",\"name\":\"certificate_select\",\"label\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[\"birth\",\"permanent_residence\"],\"valueLabel\":[{\"el\":\"Πιστοποιητικό γέννησης​\",\"en\":\"Birth certificate\",\"tr\":\"\"},{\"el\":\"Βεβαίωση μόνιμης διαμονής​\",\"en\":\"Certificate of permanent residence\",\"tr\":\"\"}]}]}]",
1198
+ "rendererData": "{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"value\":[{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[{\"element\":\"textElement\",\"params\":{\"text\":{\"en\":\"Birth certificate, Certificate of permanent residence\",\"el\":\"Birth certificate, Certificate of permanent residence\",\"tr\":\"Birth certificate, Certificate of permanent residence\"},\"type\":\"span\"}}]}]}}]}]}}",
1199
+ "rendererVersion": "1.14.3",
1200
+ "designSystemsVersion": "3.2.0",
631
1201
  "service": "{\"id\":\"test\",\"title\":{\"el\":\"Υπηρεσία τεστ\",\"en\":\"Test service\",\"tr\":\"\"}}"
632
1202
  }
633
1203
  ```
@@ -637,7 +1207,9 @@ Content-Type: application/json
637
1207
  The API is expected to return a JSON response with the following structure (see [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs) for normalization):
638
1208
 
639
1209
  **On Success:**
640
- ```json
1210
+ ```http
1211
+ HTTP/1.1 200 OK
1212
+
641
1213
  {
642
1214
  "Succeeded": true,
643
1215
  "ErrorCode": 0,
@@ -649,7 +1221,9 @@ The API is expected to return a JSON response with the following structure (see
649
1221
  ```
650
1222
 
651
1223
  **On Failure:**
652
- ```json
1224
+ ```http
1225
+ HTTP/1.1 200 OK
1226
+
653
1227
  {
654
1228
  "Succeeded": false,
655
1229
  "ErrorCode": 102,
@@ -674,15 +1248,15 @@ The API is expected to return a JSON response with the following structure (see
674
1248
  The data is collected from the form elements and the data layer and are sent via the submission API in the following format:
675
1249
 
676
1250
  ```json
677
- "submissionData": { // Site level successful submission data
678
- "submission_username" : "", // User's username
679
- "submission_email" : "", // User's email
680
- "submission_data": "{}", // Raw data as submitted by the user in each page
681
- "submission_data_version": "",// The submission data version
682
- "print_friendly_data": "[]", // Print friendly data
683
- "renderer_data" :"{}", // Renderer data of the summary list
684
- "renderer_version": "", // The renderer version
685
- "design_systems_version": "", // The design systems version
1251
+ {
1252
+ "submissionUsername" : "", // User's username
1253
+ "submissionEmail" : "", // User's email
1254
+ "submissionData": "{}", // Raw data as submitted by the user in each page
1255
+ "submissionDataVersion": "",// The submission data version
1256
+ "printFriendlyData": "[]", // Print friendly data
1257
+ "rendererData" :"{}", // Renderer data of the summary list
1258
+ "rendererVersion": "", // The renderer version
1259
+ "designSystemsVersion": "", // The design systems version
686
1260
  "service": "{}" // Service info
687
1261
  }
688
1262
  ```
@@ -693,50 +1267,49 @@ The data is collected from the form elements and the data layer and are sent via
693
1267
 
694
1268
  > ℹ️ **Note:**
695
1269
  >
696
- > When sent to the API, the fields `submission_data`, `renderer_data`, `print_friendly_data`, and `service` are stringified using `JSON.stringify()`.
1270
+ > When sent to the API, the fields `submissionData`, `rendererData`, `printFriendlyData`, and `service` are stringified using `JSON.stringify()`.
697
1271
  >
698
1272
  > The sample below shows the structure **before** stringification for clarity.
699
1273
 
700
1274
  ```json
701
1275
  {
702
- "submission_username": "username", // User's username
703
- "submission_email": "email@example.com", // User's email
704
- "submission_data_version": "0.1", // Submission data version
705
- "submission_data": { // Submission raw data. Object, will be stringified
1276
+ "submissionUsername": "username", // User's username
1277
+ "submissionEmail": "email@example.com", // User's email
1278
+ "submissionDataVersion": "0.1", // Submission data version
1279
+ "submissionData": { // Submission raw data. Object, will be stringified
706
1280
  "index": { // Page level
707
- "formData": {
708
- "id_select": ["id", "arc"], // field level. Could be string or array
709
- "id_number": "654654",
710
- "arc_number": "",
711
- "aka": "232323"
712
- }
1281
+ "id_select": ["id", "arc"], // field level: checkboxes are ALWAYS arrays (may be []); radios/select/text are strings
1282
+ "id_number": "654654",
1283
+ "arc_number": "",
1284
+ "aka": "232323",
1285
+ "evidenceAttachment": // File attachments contains an object with `fileId` and `sha256`
1286
+ {
1287
+ "fileId": "1234567891234567890",
1288
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
1289
+ }
713
1290
  },
714
1291
  "appointment": {
715
- "formData": {
716
- "diorismos": "monimos",
717
- "fileno_monimos": "3233",
718
- "eidikotita_monimos": "1",
719
- "fileno_sumvasiouxos": "",
720
- "eidikotita_sumvasiouxos": "",
721
- "fileno_aoristou": "",
722
- "eidikotita_aoristou": "",
723
- "program": "",
724
- "fileno_orismenou": ""
725
- }
1292
+ "diorismos": "monimos",
1293
+ "fileno_monimos": "3233",
1294
+ "eidikotita_monimos": "1",
1295
+ "fileno_sumvasiouxos": "",
1296
+ "eidikotita_sumvasiouxos": "",
1297
+ "fileno_aoristou": "",
1298
+ "eidikotita_aoristou": "",
1299
+ "program": "",
1300
+ "fileno_orismenou": ""
726
1301
  },
727
1302
  "takeover": {
728
- "formData": {
729
- "date_start_day": "11",
730
- "date_start_month": "12",
731
- "date_start_year": "2020",
732
- "date_on_contract": "date_other",
733
- "date_contract": "16/04/2025",
734
- "reason": "24324dssf"
735
- }
1303
+ "date_start_day": "11",
1304
+ "date_start_month": "12",
1305
+ "date_start_year": "2020",
1306
+ "date_on_contract": "date_other",
1307
+ "date_contract": "16/04/2025",
1308
+ "reason": "24324dssf"
736
1309
  }
737
1310
  },
738
- "submission_data_version": "1", // Submission data version
739
- "renderer_data": { // Summary list renderer data ready for rendering . Object, will be stringified
1311
+ "submissionDataVersion": "1", // Submission data version
1312
+ "rendererData": { // Summary list renderer data ready for rendering . Object, will be stringified
740
1313
  "element": "summaryList",
741
1314
  "params": {
742
1315
  "items": [
@@ -943,7 +1516,7 @@ The data is collected from the form elements and the data layer and are sent via
943
1516
  ]
944
1517
  }
945
1518
  },
946
- "print_friendly_data": [ // Print friendly data. Object, will be stringified
1519
+ "printFriendlyData": [ // Print friendly data. Object, will be stringified
947
1520
  {
948
1521
  "pageUrl": "index", // Page URL
949
1522
  "pageTitle": { // Page title
@@ -958,8 +1531,8 @@ The data is collected from the form elements and the data layer and are sent via
958
1531
  "el": "Ταυτοποίηση",
959
1532
  "en": "Identification"
960
1533
  },
961
- "value": ["id", "arc"], // Field value. Could be string or array
962
- "valueLabel": [ // Field value label. Could be string or array
1534
+ "value": ["id", "arc"], // Field value. // field level: checkboxes are ALWAYS arrays (may be []); radios/select/text are strings
1535
+ "valueLabel": [ // Field value label
963
1536
  {
964
1537
  "el": "Ταυτότητα",
965
1538
  "en": "ID",
@@ -1106,8 +1679,8 @@ The data is collected from the form elements and the data layer and are sent via
1106
1679
  ]
1107
1680
  }
1108
1681
  ],
1109
- "renderer_version": "1.14.1", // Renderer version
1110
- "design_systems_version": "3.1.0", // Design systems version
1682
+ "rendererVersion": "1.14.1", // Renderer version
1683
+ "designSystemsVersion": "3.1.0", // Design systems version
1111
1684
  "service": { // Service metadata. Object, will be stringified
1112
1685
  "id": "takeover",
1113
1686
  "title": {
@@ -1131,6 +1704,7 @@ The project includes input validation for the following elements:
1131
1704
  - `checkboxes`
1132
1705
  - `datePicker`
1133
1706
  - `dateInput`
1707
+ - `fileInput` (only required check)
1134
1708
 
1135
1709
  The validation rules for each element are defined in the `"validations` array for each element. The project support the following validations:
1136
1710
 
@@ -1186,7 +1760,7 @@ Example:
1186
1760
  ]
1187
1761
  ```
1188
1762
 
1189
- ### Conditional logic
1763
+ ### 🔀 Conditional logic
1190
1764
 
1191
1765
  The project supports conditional logic on pages. Conditional logic is evaluated using a custom `govcyExpressions.mjs` module, which executes expressions in a safe and scoped context using `new Function`. Only safe data access through the `dataLayer` is allowed. The system uses expressions and session data from the service's [data layer](NOTES.md#data-layer) to decide if a page will be shown or not.
1192
1766
 
@@ -1414,12 +1988,12 @@ Explanation:
1414
1988
  - `[].concat(...)`: safely flattens a string or array into an array.
1415
1989
  - `.includes('value1')`: checks if the value is selected.
1416
1990
 
1417
- ### 💾 Temporary save
1991
+ ### 💾 Temporary save feature
1418
1992
 
1419
1993
  The **temporary save** feature allows user progress to be stored in an external API and automatically reloaded on the next visit.
1420
1994
  This is useful for long forms or cases where users may leave and return later.
1421
1995
 
1422
- #### How to configure temporary save
1996
+ #### How to enable and configure temporary save
1423
1997
  To use this feature, configure the config JSON file. In your service’s `site` object, add both a `submissionGetAPIEndpoint` and `submissionPutAPIEndpoint` entry:
1424
1998
 
1425
1999
  ```json
@@ -1456,7 +2030,7 @@ TEST_SUBMISSION_API_SERVICE_ID=123
1456
2030
  3. If not found, call the PUT endpoint to create a new temporary record.
1457
2031
  - **On every form POST**, after successful validation:
1458
2032
  - The `submissionPutAPIEndpoint` will fire-and-forget a `PUT` request to update the saved submission with the latest form data.
1459
- - The payload includes all required submission fields with `submission_data` JSON-stringified.
2033
+ - The payload includes all required submission fields with `submissionData` JSON-stringified.
1460
2034
 
1461
2035
  #### `submissionGetAPIEndpoint` `GET` API Request and Response
1462
2036
  This API is used to retrieve the saved submission data.
@@ -1492,7 +2066,9 @@ The API is expected to return a JSON response with the following structure:
1492
2066
 
1493
2067
  **When temporary submission data are found:**
1494
2068
 
1495
- ```json
2069
+ ```http
2070
+ HTTP/1.1 200 OK
2071
+
1496
2072
  {
1497
2073
  "Succeeded": true,
1498
2074
  "ErrorCode": 0,
@@ -1505,7 +2081,9 @@ The API is expected to return a JSON response with the following structure:
1505
2081
 
1506
2082
  **When temporary submission data are NOT found:**
1507
2083
 
1508
- ```json
2084
+ ```http
2085
+ HTTP/1.1 404 Not Found
2086
+
1509
2087
  {
1510
2088
  "Succeeded": true,
1511
2089
  "ErrorCode": 0,
@@ -1516,7 +2094,9 @@ The API is expected to return a JSON response with the following structure:
1516
2094
 
1517
2095
  **When temporary submission retreival fails:**
1518
2096
 
1519
- ```json
2097
+ ```http
2098
+ HTTP/1.1 200 OK
2099
+
1520
2100
  {
1521
2101
  "Succeeded": false,
1522
2102
  "ErrorCode": 401,
@@ -1551,7 +2131,7 @@ Accept: text/plain
1551
2131
  Content-Type: application/json
1552
2132
 
1553
2133
  {
1554
- "submission_data" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
2134
+ "submissionData" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
1555
2135
  }
1556
2136
  ```
1557
2137
 
@@ -1561,7 +2141,9 @@ The API is expected to return a JSON response with the following structure:
1561
2141
 
1562
2142
  **On success:**
1563
2143
 
1564
- ```json
2144
+ ```http
2145
+ HTTP/1.1 200 OK
2146
+
1565
2147
  {
1566
2148
  "Succeeded": true,
1567
2149
  "ErrorCode": 0,
@@ -1574,7 +2156,9 @@ The API is expected to return a JSON response with the following structure:
1574
2156
 
1575
2157
  **On failure:**
1576
2158
 
1577
- ```json
2159
+ ```http
2160
+ HTTP/1.1 401 Unauthorized
2161
+
1578
2162
  {
1579
2163
  "Succeeded": false,
1580
2164
  "ErrorCode": 401,
@@ -1590,6 +2174,384 @@ The API is expected to return a JSON response with the following structure:
1590
2174
  If these endpoints are not defined in the service JSON, the temporary save/load logic is skipped entirely.
1591
2175
  Existing services will continue to work without modification.
1592
2176
 
2177
+ ### 🗃️ Files uploads feature
2178
+
2179
+ The **Files uploads** feature allows user progress to upload, remove and download files. The files will be stored in an external API and automatically reloaded on the next visit.
2180
+
2181
+ #### Pre-requisites for the files uploads feature
2182
+ The [💾 Temporary save feature](#-temporary-save-feature) must be enabled for the file uploads feature to work.
2183
+
2184
+
2185
+ #### How to enable and configure file uploads
2186
+ To use this feature, configure the config JSON file. In your service’s `site` object, add a `fileUploadAPIEndpoint`, `fileDownloadAPIEndpoint`and `fileDeleteAPIEndpoint` entry:
2187
+
2188
+ ```json
2189
+ "fileUploadAPIEndpoint": {
2190
+ "url": "TEST_UPLOAD_FILE_API_URL",
2191
+ "method": "POST",
2192
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2193
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2194
+ },
2195
+ "fileDownloadAPIEndpoint": {
2196
+ "url": "TEST_DOWNLOAD_FILE_API_URL",
2197
+ "method": "GET",
2198
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2199
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2200
+ },
2201
+ "fileDeleteAPIEndpoint": {
2202
+ "url": "TEST_DELETE_FILE_API_URL",
2203
+ "method": "DELETE",
2204
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2205
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2206
+ }
2207
+ ```
2208
+
2209
+ These values should point to environment variables that hold your real endpoint URLs and credentials.
2210
+
2211
+ In your `secrets/.env` file (and staging/production configs), define the variables referenced above:
2212
+
2213
+ ```dotenv
2214
+ TEST_UPLOAD_FILE_API_URL=https://example.com/api/fileUpload
2215
+ TEST_DOWNLOAD_FILE_API_URL=https://example.com/api/fileDownload
2216
+ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
2217
+ TEST_SUBMISSION_API_SERVICE_ID=123
2218
+ ```
2219
+
2220
+
2221
+ #### How to define the file input field in the JSON config
2222
+ The files input field is defined under the `pages[i].pageTemplate.sections["main"].elements[0].elements` array. Basically it must exist in the page template of a page inside a form.
2223
+
2224
+ The element type for files input is `fileInput` (see it's definition in [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md))
2225
+
2226
+ Here is a sample code section of a page definition with a file input field:
2227
+
2228
+ ```json
2229
+ {
2230
+ "pageData": {
2231
+ "url": "data-entry-file",
2232
+ "title": {
2233
+ "el": "Λογαριασμός κινήσεως",
2234
+ "en": "Utility bill"
2235
+ },
2236
+ "layout": "layouts/govcyBase.njk",
2237
+ "mainLayout": "two-third",
2238
+ "nextPage": "data-entry-all"
2239
+ },
2240
+ "pageTemplate": {
2241
+ "sections": [
2242
+ {
2243
+ "name": "beforeMain",
2244
+ "elements": [
2245
+ {
2246
+ "element": "backLink",
2247
+ "params": {}
2248
+ }
2249
+ ]
2250
+ },
2251
+ {
2252
+ "name": "main",
2253
+ "elements": [
2254
+ {
2255
+ "element": "form",
2256
+ "params": {
2257
+ "elements": [
2258
+ {
2259
+ "element": "fileInput", //<-- this is the file input
2260
+ "params": {
2261
+ "id": "utility",
2262
+ "name": "utility",
2263
+ "label": {
2264
+ "el": "Λογαριασμός κινήσεως",
2265
+ "en": "Utility bill"
2266
+ },
2267
+ "isPageHeading": true,
2268
+ "hint": {
2269
+ "el": "PDF, JPG, JPEG, PNG, είναι οι αποδεκτές μορφές",
2270
+ "en": "PDF, JPG, JPEG, PNG are the acceptable formats"
2271
+ }
2272
+ },
2273
+ "validations": [ //<-- this is the file input validations
2274
+ {
2275
+ "check": "required",
2276
+ "params": {
2277
+ "checkValue": "",
2278
+ "message": {
2279
+ "el": "Ανεβάστε τον λογαριασμό κινήσεως",
2280
+ "en": "Upload the utility bill"
2281
+ }
2282
+ }
2283
+ }
2284
+ ]
2285
+ },
2286
+ {
2287
+ "element": "button",
2288
+ "params": {
2289
+ "id": "continue",
2290
+ "variant": "primary",
2291
+ "text": {
2292
+ "el": "Συνέχεια",
2293
+ "en": "Continue"
2294
+ }
2295
+ }
2296
+ }
2297
+ ]
2298
+ }
2299
+ }
2300
+ ]
2301
+ }
2302
+ ]
2303
+ }
2304
+ }
2305
+ ```
2306
+
2307
+ #### How file uploads works
2308
+
2309
+ - **On a form page with an upload input field**:
2310
+ - **On first load**: the page displays the fileInput field to choose a file.
2311
+ - **On choosing a file**: the file is uploaded to the `fileUploadAPIEndpoint` endpoint.
2312
+ - The `fileUploadAPIEndpoint` validates the input for allowed file types and file size. On validation error an error message is displayed. The following validations are performed:
2313
+ - The fileInput element is defined in the JSON config
2314
+ - API endpoints and environment variables are defined
2315
+ - The page is not skiped because of conditional logic
2316
+ - The file is not empty
2317
+ - The file size must be less than 5MB
2318
+ - The file type must be one of the following: pdf, jpg, jpeg, png
2319
+ - If `fileUploadAPIEndpoint` returns an success, the file is uploaded temporarilly and the `fileView` element is displayed, with links to `View` or `Delete` the file. The file infomation are store in the data layer of the page.
2320
+ - **On a form page after upload**:
2321
+ - The `fileView` element is displayed with links to `View` or `Delete` the file.
2322
+ - **When clinking `view`**:
2323
+ - The file is downloaded using the `fileDownloadAPIEndpoint` and opened in a new tab.
2324
+ - **When clinking `delete`**:
2325
+ - A confimation page is displayed asking the user to confirm the deletion. If the user confirms (clicks `Yes`):
2326
+ - The file is deleted using the `fileDeleteAPIEndpoint`
2327
+ - The file is deleted from the data layer and the `fileView` element is removed from the page.
2328
+ - If the same file is used on another page (with the same `fileId` and `sha256`), they are also removed from the data layer
2329
+ - **On the `review` page after upload**:
2330
+ - The element is displayed with a link to `View file`. A `Change` link is also displayed for the whole page.
2331
+ - **On the `success` page and email after upload**:
2332
+ - The element is displayed with links a marking `File uploaded`.
2333
+
2334
+
2335
+ #### `fileUploadAPIEndpoint` `POST` API Request and Response
2336
+ This API is used to temporarily store the file uploaded by the user. The API connects the file with a temporary saved submission, for the specific user and service. It does not create a connection with the actual field on the specific page, that is done by the `submissionPutAPIEndpoint` API.
2337
+
2338
+ **Request:**
2339
+
2340
+ - **HTTP Method**: POST
2341
+ - **URL**: Resolved from the url property in your config (from the environment variable) concatenated with `/:tag` which defines the type of the file (for example `passport`). For example `https://example.com/api/fileUpload/:tag`
2342
+ - **Headers**:
2343
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2344
+ - **client-key**: `<clientKey>` (from config/env)
2345
+ - **service-id**: `<serviceId>` (from config/env)
2346
+ - **Accept**: `application/json`
2347
+ - **Content-Type**: `multipart/form-data` (typically automatically set when using FormData in the browser)
2348
+ - **Body (multipart/form-data)**: The body the actual file to be uploaded (PDF, JPEG, etc.)
2349
+
2350
+ **Example Request:**
2351
+
2352
+ ``` bash
2353
+ curl --location 'https://example.gov.cy/api/v1/files/upload/passport' \
2354
+ --header 'client-key: 12345678901234567890123456789000' \
2355
+ --header 'service-id: 123' \
2356
+ --header 'Authorization: Bearer eyJhbGciOi...' \
2357
+ --form 'file=@"/path/to/file.pdf"'
2358
+ ```
2359
+
2360
+ **Response:**
2361
+
2362
+ The API is expected to return a JSON response with the following structure:
2363
+
2364
+ **On success:**
2365
+
2366
+ ```http
2367
+ HTTP/1.1 200 OK
2368
+
2369
+ {
2370
+ "ErrorCode": 0,
2371
+ "ErrorMessage": null,
2372
+ "Data": {
2373
+ "fileId": "6899adac8864bf90a90047c3",
2374
+ "fileName": "passport.pdf",
2375
+ "contentType": "application/pdf",
2376
+ "fileSize": 4721123,
2377
+ "sha256": "8adb79e0e782280dad8beb227333a21796b8e01d019ab1e84cfea89a523b0e7d",
2378
+ "description": "passport.pdf",
2379
+ "tag": "passport"
2380
+ },
2381
+ "Succeeded": true
2382
+ }
2383
+ ```
2384
+
2385
+ **On failure:**
2386
+
2387
+ ```http
2388
+ HTTP/1.1 400
2389
+
2390
+ {
2391
+ "ErrorCode": 400,
2392
+ "ErrorMessage": "SUBMISSION_REQUIRED",
2393
+ "Data": null,
2394
+ "Succeeded": false
2395
+ }
2396
+ ```
2397
+
2398
+ #### `fileDownloadAPIEndpoint` `GET` API Request and Response
2399
+ This API is used to download the file uploaded by the user. It returns the file data in Base64.
2400
+
2401
+ **Request:**
2402
+
2403
+ - **HTTP Method**: GET
2404
+ - **URL**: Resolved from the url property in your config (from the environment variable) concatenated with `/:refernceValue/:fileid/:sha256`. For example `https://example.com/api/fileDownload/1234567890/123456789123456/12345678901234567890123`:
2405
+ - `referenceValue` is the `referenceValue` of the file current temporary saved instance (see more [💾 Temporary save feature](#-temporary-save-feature)).
2406
+ - `fileid` is the `fileId` of the file uploaded by the user.
2407
+ - `sha256` is the `sha256` of the file uploaded by the user.
2408
+ - **Headers**:
2409
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2410
+ - **client-key**: `<clientKey>` (from config/env)
2411
+ - **service-id**: `<serviceId>` (from config/env)
2412
+ - **Accept**: `text/plain`
2413
+
2414
+ **Example Request:**
2415
+
2416
+ ```http
2417
+ GET fileDownload/1234567890/123456789123456/12345678901234567890123 HTTP/1.1
2418
+ Host: localhost:3002
2419
+ Authorization: Bearer eyJhbGciOi...
2420
+ client-key: 12345678901234567890123456789000
2421
+ service-id: 123
2422
+ Accept: text/plain
2423
+ Content-Type: application/json
2424
+ ```
2425
+
2426
+ **Response:**
2427
+
2428
+ The API is expected to return a JSON response with the following structure:
2429
+
2430
+ **When file is found:**
2431
+
2432
+ ```http
2433
+ HTTP/1.1 200 OK
2434
+
2435
+ {
2436
+ "ErrorCode": 0,
2437
+ "ErrorMessage": null,
2438
+ "Data": {
2439
+ "fileId": "123456789123456",
2440
+ "fileName": "passport.pdf",
2441
+ "contentType": "application/pdf",
2442
+ "fileSize": 1872,
2443
+ "sha256": "12345678901234567890123456789012345678901234567890123456789012345",
2444
+ "base64": "JVBERi0xLjMKJZOMi54gUm....
2445
+ ",
2446
+ "description": null,
2447
+ "uid": null,
2448
+ "tag": "Passport"
2449
+ },
2450
+ "Succeeded": true
2451
+ }
2452
+ ```
2453
+
2454
+ **When file is NOT found:**
2455
+
2456
+ ```http
2457
+ HTTP/1.1 404 Not Found
2458
+
2459
+ {
2460
+ "ErrorCode": 404,
2461
+ "ErrorMessage": "File not found",
2462
+ "InformationMessage": null,
2463
+ "Data": null,
2464
+ "Succeeded": false
2465
+ }
2466
+ ```
2467
+
2468
+ #### `fileDeleteAPIEndpoint` `DELETE` API Request and Response
2469
+ This API is used to delete the file uploaded by the user. It returns the file data in Base64.
2470
+ > ⚠️ **Important note:**
2471
+ > If the same file (same `fileId` and `sha256`) is used for other fields in the same application for the same service and the same user, when deleted it will be removed from all instances in the data layer. A warning will appear in the user's delete confirmation page to warn the users in such cases.
2472
+
2473
+ **Request:**
2474
+
2475
+ - **HTTP Method**: DELETE
2476
+ - **URL**: Resolved from the url property in your config (from the environment variable) concatenated with `/:fileid/:sha256`. For example `https://example.com/api/fileDelete/123456789123456/12345678901234567890123`:
2477
+ - `fileid` is the `fileId` of the file uploaded by the user.
2478
+ - `sha256` is the `sha256` of the file uploaded by the user.
2479
+ - **Headers**:
2480
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2481
+ - **client-key**: `<clientKey>` (from config/env)
2482
+ - **service-id**: `<serviceId>` (from config/env)
2483
+ - **Accept**: `text/plain`
2484
+
2485
+ **Example Request:**
2486
+
2487
+ ```http
2488
+ DELETE fileDelete/123456789123456/12345678901234567890123 HTTP/1.1
2489
+ Host: localhost:3002
2490
+ Authorization: Bearer eyJhbGciOi...
2491
+ client-key: 12345678901234567890123456789000
2492
+ service-id: 123
2493
+ Accept: text/plain
2494
+ Content-Type: application/json
2495
+ ```
2496
+
2497
+ **Response:**
2498
+
2499
+ The API is expected to return a JSON response with the following structure:
2500
+
2501
+ **When file is found and deleted:**
2502
+
2503
+ ```http
2504
+ HTTP/1.1 200 OK
2505
+
2506
+ {
2507
+ "ErrorCode": 0,
2508
+ "ErrorMessage": null,
2509
+ "Succeeded": true
2510
+ }
2511
+ ```
2512
+
2513
+ **When file is NOT found:**
2514
+
2515
+ ```http
2516
+ HTTP/1.1 404 Not Found
2517
+
2518
+ {
2519
+ "ErrorCode": 404,
2520
+ "ErrorMessage": "File not found",
2521
+ "InformationMessage": null,
2522
+ "Data": null,
2523
+ "Succeeded": false
2524
+ }
2525
+ ```
2526
+
2527
+ #### File uploads in the data layer
2528
+ The system uses the `fileId` and `sha256` to identify the file uploaded by the user. The file information are stored in the data layer in the following format:
2529
+
2530
+ ```json
2531
+ {
2532
+ "fileId": "1234567891234567890",
2533
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
2534
+ }
2535
+ ```
2536
+
2537
+ #### File uploads when submitted
2538
+ When the user submits the service through the submission API endpoint, the system will use the `fileId` and `sha256` to identify the file uploaded by the user. The actuall file is already uploaded via the `fileUploadAPIEndpoint`.
2539
+
2540
+ To help back-end systems recognize the field as a file, the field's element name is concatenated with `Attachment`. For example, if for the field `passport`, the data will be submitted as follows:
2541
+
2542
+ ```json
2543
+ {
2544
+ "passportAttachment": {
2545
+ "fileId": "1234567891234567890",
2546
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
2547
+ }
2548
+ }
2549
+ ```
2550
+
2551
+ #### File uploads backward compatibility
2552
+ If these endpoints are not defined in the service JSON, the file upload logic is skipped entirely.
2553
+ Existing services will continue to work without modification.
2554
+
1593
2555
  ### 🛣️ Routes
1594
2556
  The project uses express.js to serve the following routes:
1595
2557
 
@@ -1598,16 +2560,18 @@ The project uses express.js to serve the following routes:
1598
2560
  - **`/:siteId/:pageUrl`**: Requires **cyLogin** authentication for **authorized individual users**. Based on `/data/:siteId.json`, Renders the specified page template. Validates page and saves data to session. If validation fails, errors are displayed with links to the inputs.
1599
2561
  - **`/:siteId/review`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the check your answers page template. Validates all pages in the service and submits the data to the configured API endpoint. If validation fails, errors are displayed with links to the relevant pages.
1600
2562
  - **`/:siteId/success`**: Requires **cyLogin** authentication for **authorized individual users**. Renders latest successful submission.
1601
- - **`/:siteId/success/pdf`**: Requires **cyLogin** authentication for **authorized individual users**. Downloads the PDF of the latest successful submission.
2563
+ - **`/:siteId/:pageUrl/view-file/:elementName`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the specified file in a new tab.
2564
+ - **`/:siteId/:pageUrl/delete-file/:elementName`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the delete confirmation page and handles the file delete.
1602
2565
 
1603
2566
  #### Authentication routes:
1604
2567
  - **`/signin-oidc`**: CY Login authentication endpoint.
1605
2568
  - **`/login`**: Redirect to CY Login login page.
1606
2569
  - **`/logout`**: CY Login logout endpoint.
1607
2570
 
1608
- Absolutely! Here’s a **ready-to-paste Troubleshooting / FAQ section** you can add near the end of your README, just before Credits or Developer notes.
2571
+ #### API routes:
2572
+ - **`/apis/:siteId/:pageUrl/upload`**: Uploads a file. Used from the client side JS.
1609
2573
 
1610
- ### 👨‍💻 Enviromental variables
2574
+ ### 👨‍💻 Environment variables
1611
2575
  The environment variables are defined in:
1612
2576
  - **Secret environment variables**: These are secret variables and MUSR NOT be saved in version control. The are saved locally in the `secrets/.env` file and they control the server configuration, authentication, integrations, and development behavior. These variables vary depending on the environment and are defined through the deployment prosses for `staging` and `production`.
1613
2577
  - **Non secret environment variables**: These are non secret enviromentat variables and can be saved in version control. These are stored in the root folder of the project:
@@ -1678,10 +2642,14 @@ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
1678
2642
  TEST_SUBMISSION_API_SERVIVE_ID=123
1679
2643
  TEST_SUBMISSION_DSF_GTW_KEY=12345678901234567890123456789000
1680
2644
 
1681
- # Optional Temporary Save GET and PUT endpoint (test service)
2645
+ # Optional Temporary Save GET and PUT endpoints (test service)
1682
2646
  TEST_SUBMISSION_GET_API_URL=http://localhost:3002/getTempSubmission
1683
2647
  TEST_SUBMISSION_PUT_API_URL=http://localhost:3002/save
1684
2648
 
2649
+ # Optional File Upload and download endpoints (test service)
2650
+ TEST_FILE_UPLOAD_API_URL=http://localhost:3002/fileUpload
2651
+ TEST_FILE_DOWNLOAD_API_URL=http://localhost:3002/fileDownload
2652
+ TEST_FILE_DELETE_API_URL=http://localhost:3002/fileDelete
1685
2653
 
1686
2654
  # Eligibility checks (optional test APIs)
1687
2655
  TEST_ELIGIBILITY_1_API_URL=http://localhost:3002/eligibility1