@gov-cy/govcy-express-services 1.0.0-alpha.9 → 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.
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)](coverage-summary.json)
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:
@@ -624,14 +1190,14 @@ Accept: text/plain
624
1190
  Content-Type: application/json
625
1191
 
626
1192
  {
627
- "submission_username": "username",
628
- "submission_email": "email@example.com",
629
- "submission_data": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}}}",
630
- "submission_data_version": "1",
631
- "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\":\"\"}]}]}]",
632
- "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\"}}]}]}}]}]}}",
633
- "renderer_version": "1.14.3",
634
- "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",
635
1201
  "service": "{\"id\":\"test\",\"title\":{\"el\":\"Υπηρεσία τεστ\",\"en\":\"Test service\",\"tr\":\"\"}}"
636
1202
  }
637
1203
  ```
@@ -682,15 +1248,15 @@ HTTP/1.1 200 OK
682
1248
  The data is collected from the form elements and the data layer and are sent via the submission API in the following format:
683
1249
 
684
1250
  ```json
685
- "submissionData": { // Site level successful submission data
686
- "submission_username" : "", // User's username
687
- "submission_email" : "", // User's email
688
- "submission_data": "{}", // Raw data as submitted by the user in each page
689
- "submission_data_version": "",// The submission data version
690
- "print_friendly_data": "[]", // Print friendly data
691
- "renderer_data" :"{}", // Renderer data of the summary list
692
- "renderer_version": "", // The renderer version
693
- "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
694
1260
  "service": "{}" // Service info
695
1261
  }
696
1262
  ```
@@ -701,50 +1267,49 @@ The data is collected from the form elements and the data layer and are sent via
701
1267
 
702
1268
  > ℹ️ **Note:**
703
1269
  >
704
- > 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()`.
705
1271
  >
706
1272
  > The sample below shows the structure **before** stringification for clarity.
707
1273
 
708
1274
  ```json
709
1275
  {
710
- "submission_username": "username", // User's username
711
- "submission_email": "email@example.com", // User's email
712
- "submission_data_version": "0.1", // Submission data version
713
- "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
714
1280
  "index": { // Page level
715
- "formData": {
716
- "id_select": ["id", "arc"], // field level. Could be string or array
717
- "id_number": "654654",
718
- "arc_number": "",
719
- "aka": "232323"
720
- }
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
+ }
721
1290
  },
722
1291
  "appointment": {
723
- "formData": {
724
- "diorismos": "monimos",
725
- "fileno_monimos": "3233",
726
- "eidikotita_monimos": "1",
727
- "fileno_sumvasiouxos": "",
728
- "eidikotita_sumvasiouxos": "",
729
- "fileno_aoristou": "",
730
- "eidikotita_aoristou": "",
731
- "program": "",
732
- "fileno_orismenou": ""
733
- }
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": ""
734
1301
  },
735
1302
  "takeover": {
736
- "formData": {
737
- "date_start_day": "11",
738
- "date_start_month": "12",
739
- "date_start_year": "2020",
740
- "date_on_contract": "date_other",
741
- "date_contract": "16/04/2025",
742
- "reason": "24324dssf"
743
- }
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"
744
1309
  }
745
1310
  },
746
- "submission_data_version": "1", // Submission data version
747
- "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
748
1313
  "element": "summaryList",
749
1314
  "params": {
750
1315
  "items": [
@@ -951,7 +1516,7 @@ The data is collected from the form elements and the data layer and are sent via
951
1516
  ]
952
1517
  }
953
1518
  },
954
- "print_friendly_data": [ // Print friendly data. Object, will be stringified
1519
+ "printFriendlyData": [ // Print friendly data. Object, will be stringified
955
1520
  {
956
1521
  "pageUrl": "index", // Page URL
957
1522
  "pageTitle": { // Page title
@@ -966,8 +1531,8 @@ The data is collected from the form elements and the data layer and are sent via
966
1531
  "el": "Ταυτοποίηση",
967
1532
  "en": "Identification"
968
1533
  },
969
- "value": ["id", "arc"], // Field value. Could be string or array
970
- "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
971
1536
  {
972
1537
  "el": "Ταυτότητα",
973
1538
  "en": "ID",
@@ -1114,8 +1679,8 @@ The data is collected from the form elements and the data layer and are sent via
1114
1679
  ]
1115
1680
  }
1116
1681
  ],
1117
- "renderer_version": "1.14.1", // Renderer version
1118
- "design_systems_version": "3.1.0", // Design systems version
1682
+ "rendererVersion": "1.14.1", // Renderer version
1683
+ "designSystemsVersion": "3.1.0", // Design systems version
1119
1684
  "service": { // Service metadata. Object, will be stringified
1120
1685
  "id": "takeover",
1121
1686
  "title": {
@@ -1139,6 +1704,7 @@ The project includes input validation for the following elements:
1139
1704
  - `checkboxes`
1140
1705
  - `datePicker`
1141
1706
  - `dateInput`
1707
+ - `fileInput` (only required check)
1142
1708
 
1143
1709
  The validation rules for each element are defined in the `"validations` array for each element. The project support the following validations:
1144
1710
 
@@ -1194,7 +1760,7 @@ Example:
1194
1760
  ]
1195
1761
  ```
1196
1762
 
1197
- ### Conditional logic
1763
+ ### 🔀 Conditional logic
1198
1764
 
1199
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.
1200
1766
 
@@ -1422,12 +1988,12 @@ Explanation:
1422
1988
  - `[].concat(...)`: safely flattens a string or array into an array.
1423
1989
  - `.includes('value1')`: checks if the value is selected.
1424
1990
 
1425
- ### 💾 Temporary save
1991
+ ### 💾 Temporary save feature
1426
1992
 
1427
1993
  The **temporary save** feature allows user progress to be stored in an external API and automatically reloaded on the next visit.
1428
1994
  This is useful for long forms or cases where users may leave and return later.
1429
1995
 
1430
- #### How to configure temporary save
1996
+ #### How to enable and configure temporary save
1431
1997
  To use this feature, configure the config JSON file. In your service’s `site` object, add both a `submissionGetAPIEndpoint` and `submissionPutAPIEndpoint` entry:
1432
1998
 
1433
1999
  ```json
@@ -1464,7 +2030,7 @@ TEST_SUBMISSION_API_SERVICE_ID=123
1464
2030
  3. If not found, call the PUT endpoint to create a new temporary record.
1465
2031
  - **On every form POST**, after successful validation:
1466
2032
  - The `submissionPutAPIEndpoint` will fire-and-forget a `PUT` request to update the saved submission with the latest form data.
1467
- - The payload includes all required submission fields with `submission_data` JSON-stringified.
2033
+ - The payload includes all required submission fields with `submissionData` JSON-stringified.
1468
2034
 
1469
2035
  #### `submissionGetAPIEndpoint` `GET` API Request and Response
1470
2036
  This API is used to retrieve the saved submission data.
@@ -1565,7 +2131,7 @@ Accept: text/plain
1565
2131
  Content-Type: application/json
1566
2132
 
1567
2133
  {
1568
- "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\"}}}"
1569
2135
  }
1570
2136
  ```
1571
2137
 
@@ -1608,6 +2174,384 @@ HTTP/1.1 401 Unauthorized
1608
2174
  If these endpoints are not defined in the service JSON, the temporary save/load logic is skipped entirely.
1609
2175
  Existing services will continue to work without modification.
1610
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 4MB
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
+
1611
2555
  ### 🛣️ Routes
1612
2556
  The project uses express.js to serve the following routes:
1613
2557
 
@@ -1616,16 +2560,18 @@ The project uses express.js to serve the following routes:
1616
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.
1617
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.
1618
2562
  - **`/:siteId/success`**: Requires **cyLogin** authentication for **authorized individual users**. Renders latest successful submission.
1619
- - **`/: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.
1620
2565
 
1621
2566
  #### Authentication routes:
1622
2567
  - **`/signin-oidc`**: CY Login authentication endpoint.
1623
2568
  - **`/login`**: Redirect to CY Login login page.
1624
2569
  - **`/logout`**: CY Login logout endpoint.
1625
2570
 
1626
- 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.
1627
2573
 
1628
- ### 👨‍💻 Enviromental variables
2574
+ ### 👨‍💻 Environment variables
1629
2575
  The environment variables are defined in:
1630
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`.
1631
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:
@@ -1696,10 +2642,14 @@ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
1696
2642
  TEST_SUBMISSION_API_SERVIVE_ID=123
1697
2643
  TEST_SUBMISSION_DSF_GTW_KEY=12345678901234567890123456789000
1698
2644
 
1699
- # Optional Temporary Save GET and PUT endpoint (test service)
2645
+ # Optional Temporary Save GET and PUT endpoints (test service)
1700
2646
  TEST_SUBMISSION_GET_API_URL=http://localhost:3002/getTempSubmission
1701
2647
  TEST_SUBMISSION_PUT_API_URL=http://localhost:3002/save
1702
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
1703
2653
 
1704
2654
  # Eligibility checks (optional test APIs)
1705
2655
  TEST_ELIGIBILITY_1_API_URL=http://localhost:3002/eligibility1