@gov-cy/govcy-express-services 1.0.0-alpha.9 → 1.1.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,15 +142,561 @@ 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
+ "submissionDataVersion": "1", //<-- Submission data version
277
+ "rendererVersion": "1.16.1", //<-- govcy-frontend-renderer version
278
+ "designSystemsVersion": "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:
146
- - `submission_data_version` : The submission data version,
147
- - `renderer_version` : The govcy-frontend-renderer version,
148
- - `design_systems_version` : The govcy-design-system version,
697
+ - `submissionDataVersion` : The submission data version,
698
+ - `rendererVersion` : The govcy-frontend-renderer version,
699
+ - `designSystemsVersion` : The govcy-design-system version,
149
700
  - `homeRedirectPage`: An object mapping language codes to URLs. When a user visits the root route (e.g., `https://whatever-your-service-is.service.gov.cy/`), the system redirects to the URL for the user's language. If the user's language is not found, it falls back to `"el"` or the first available URL. If not provided, a list of available sites is shown. Example:
150
701
  ```json
151
702
  "homeRedirectPage": {
@@ -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.
@@ -1508,11 +2074,14 @@ HTTP/1.1 200 OK
1508
2074
  "ErrorCode": 0,
1509
2075
  "ErrorMessage": null,
1510
2076
  "Data": {
1511
- "submissionData": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
2077
+ "submissionData": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}",
2078
+ "referenceValue": "0000000924107836"
1512
2079
  }
1513
2080
  }
1514
2081
  ```
1515
2082
 
2083
+ **NOTE**: The `Data` object contains the `submissionData` and `referenceValue` fields. The `referenceValue` is a unique identifier for the submission. It is used to identify the submission in the `submissionPutAPIEndpoint` and `fileDownloadAPIEndpoint` API requests.
2084
+
1516
2085
  **When temporary submission data are NOT found:**
1517
2086
 
1518
2087
  ```http
@@ -1565,7 +2134,7 @@ Accept: text/plain
1565
2134
  Content-Type: application/json
1566
2135
 
1567
2136
  {
1568
- "submission_data" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
2137
+ "submissionData" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
1569
2138
  }
1570
2139
  ```
1571
2140
 
@@ -1583,7 +2152,8 @@ HTTP/1.1 200 OK
1583
2152
  "ErrorCode": 0,
1584
2153
  "ErrorMessage": null,
1585
2154
  "Data": {
1586
- "submissionData": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
2155
+ "submissionData": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}",
2156
+ "referenceValue": "0000000924107836"
1587
2157
  }
1588
2158
  }
1589
2159
  ```
@@ -1608,6 +2178,384 @@ HTTP/1.1 401 Unauthorized
1608
2178
  If these endpoints are not defined in the service JSON, the temporary save/load logic is skipped entirely.
1609
2179
  Existing services will continue to work without modification.
1610
2180
 
2181
+ ### 🗃️ Files uploads feature
2182
+
2183
+ 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.
2184
+
2185
+ #### Pre-requisites for the files uploads feature
2186
+ The [💾 Temporary save feature](#-temporary-save-feature) must be enabled for the file uploads feature to work.
2187
+
2188
+
2189
+ #### How to enable and configure file uploads
2190
+ To use this feature, configure the config JSON file. In your service’s `site` object, add a `fileUploadAPIEndpoint`, `fileDownloadAPIEndpoint`and `fileDeleteAPIEndpoint` entry:
2191
+
2192
+ ```json
2193
+ "fileUploadAPIEndpoint": {
2194
+ "url": "TEST_UPLOAD_FILE_API_URL",
2195
+ "method": "POST",
2196
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2197
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2198
+ },
2199
+ "fileDownloadAPIEndpoint": {
2200
+ "url": "TEST_DOWNLOAD_FILE_API_URL",
2201
+ "method": "GET",
2202
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2203
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2204
+ },
2205
+ "fileDeleteAPIEndpoint": {
2206
+ "url": "TEST_DELETE_FILE_API_URL",
2207
+ "method": "DELETE",
2208
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2209
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2210
+ }
2211
+ ```
2212
+
2213
+ These values should point to environment variables that hold your real endpoint URLs and credentials.
2214
+
2215
+ In your `secrets/.env` file (and staging/production configs), define the variables referenced above:
2216
+
2217
+ ```dotenv
2218
+ TEST_UPLOAD_FILE_API_URL=https://example.com/api/fileUpload
2219
+ TEST_DOWNLOAD_FILE_API_URL=https://example.com/api/fileDownload
2220
+ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
2221
+ TEST_SUBMISSION_API_SERVICE_ID=123
2222
+ ```
2223
+
2224
+
2225
+ #### How to define the file input field in the JSON config
2226
+ 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.
2227
+
2228
+ 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))
2229
+
2230
+ Here is a sample code section of a page definition with a file input field:
2231
+
2232
+ ```json
2233
+ {
2234
+ "pageData": {
2235
+ "url": "data-entry-file",
2236
+ "title": {
2237
+ "el": "Λογαριασμός κινήσεως",
2238
+ "en": "Utility bill"
2239
+ },
2240
+ "layout": "layouts/govcyBase.njk",
2241
+ "mainLayout": "two-third",
2242
+ "nextPage": "data-entry-all"
2243
+ },
2244
+ "pageTemplate": {
2245
+ "sections": [
2246
+ {
2247
+ "name": "beforeMain",
2248
+ "elements": [
2249
+ {
2250
+ "element": "backLink",
2251
+ "params": {}
2252
+ }
2253
+ ]
2254
+ },
2255
+ {
2256
+ "name": "main",
2257
+ "elements": [
2258
+ {
2259
+ "element": "form",
2260
+ "params": {
2261
+ "elements": [
2262
+ {
2263
+ "element": "fileInput", //<-- this is the file input
2264
+ "params": {
2265
+ "id": "utility",
2266
+ "name": "utility",
2267
+ "label": {
2268
+ "el": "Λογαριασμός κινήσεως",
2269
+ "en": "Utility bill"
2270
+ },
2271
+ "isPageHeading": true,
2272
+ "hint": {
2273
+ "el": "PDF, JPG, JPEG, PNG, είναι οι αποδεκτές μορφές",
2274
+ "en": "PDF, JPG, JPEG, PNG are the acceptable formats"
2275
+ }
2276
+ },
2277
+ "validations": [ //<-- this is the file input validations
2278
+ {
2279
+ "check": "required",
2280
+ "params": {
2281
+ "checkValue": "",
2282
+ "message": {
2283
+ "el": "Ανεβάστε τον λογαριασμό κινήσεως",
2284
+ "en": "Upload the utility bill"
2285
+ }
2286
+ }
2287
+ }
2288
+ ]
2289
+ },
2290
+ {
2291
+ "element": "button",
2292
+ "params": {
2293
+ "id": "continue",
2294
+ "variant": "primary",
2295
+ "text": {
2296
+ "el": "Συνέχεια",
2297
+ "en": "Continue"
2298
+ }
2299
+ }
2300
+ }
2301
+ ]
2302
+ }
2303
+ }
2304
+ ]
2305
+ }
2306
+ ]
2307
+ }
2308
+ }
2309
+ ```
2310
+
2311
+ #### How file uploads works
2312
+
2313
+ - **On a form page with an upload input field**:
2314
+ - **On first load**: the page displays the fileInput field to choose a file.
2315
+ - **On choosing a file**: the file is uploaded to the `fileUploadAPIEndpoint` endpoint.
2316
+ - 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:
2317
+ - The fileInput element is defined in the JSON config
2318
+ - API endpoints and environment variables are defined
2319
+ - The page is not skiped because of conditional logic
2320
+ - The file is not empty
2321
+ - The file size must be less than 4MB
2322
+ - The file type must be one of the following: pdf, jpg, jpeg, png
2323
+ - 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.
2324
+ - **On a form page after upload**:
2325
+ - The `fileView` element is displayed with links to `View` or `Delete` the file.
2326
+ - **When clinking `view`**:
2327
+ - The file is downloaded using the `fileDownloadAPIEndpoint` and opened in a new tab.
2328
+ - **When clinking `delete`**:
2329
+ - A confimation page is displayed asking the user to confirm the deletion. If the user confirms (clicks `Yes`):
2330
+ - The file is deleted using the `fileDeleteAPIEndpoint`
2331
+ - The file is deleted from the data layer and the `fileView` element is removed from the page.
2332
+ - If the same file is used on another page (with the same `fileId` and `sha256`), they are also removed from the data layer
2333
+ - **On the `review` page after upload**:
2334
+ - The element is displayed with a link to `View file`. A `Change` link is also displayed for the whole page.
2335
+ - **On the `success` page and email after upload**:
2336
+ - The element is displayed with links a marking `File uploaded`.
2337
+
2338
+
2339
+ #### `fileUploadAPIEndpoint` `POST` API Request and Response
2340
+ 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.
2341
+
2342
+ **Request:**
2343
+
2344
+ - **HTTP Method**: POST
2345
+ - **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`
2346
+ - **Headers**:
2347
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2348
+ - **client-key**: `<clientKey>` (from config/env)
2349
+ - **service-id**: `<serviceId>` (from config/env)
2350
+ - **Accept**: `application/json`
2351
+ - **Content-Type**: `multipart/form-data` (typically automatically set when using FormData in the browser)
2352
+ - **Body (multipart/form-data)**: The body the actual file to be uploaded (PDF, JPEG, etc.)
2353
+
2354
+ **Example Request:**
2355
+
2356
+ ``` bash
2357
+ curl --location 'https://example.gov.cy/api/v1/files/upload/passport' \
2358
+ --header 'client-key: 12345678901234567890123456789000' \
2359
+ --header 'service-id: 123' \
2360
+ --header 'Authorization: Bearer eyJhbGciOi...' \
2361
+ --form 'file=@"/path/to/file.pdf"'
2362
+ ```
2363
+
2364
+ **Response:**
2365
+
2366
+ The API is expected to return a JSON response with the following structure:
2367
+
2368
+ **On success:**
2369
+
2370
+ ```http
2371
+ HTTP/1.1 200 OK
2372
+
2373
+ {
2374
+ "ErrorCode": 0,
2375
+ "ErrorMessage": null,
2376
+ "Data": {
2377
+ "fileId": "6899adac8864bf90a90047c3",
2378
+ "fileName": "passport.pdf",
2379
+ "contentType": "application/pdf",
2380
+ "fileSize": 4721123,
2381
+ "sha256": "8adb79e0e782280dad8beb227333a21796b8e01d019ab1e84cfea89a523b0e7d",
2382
+ "description": "passport.pdf",
2383
+ "tag": "passport"
2384
+ },
2385
+ "Succeeded": true
2386
+ }
2387
+ ```
2388
+
2389
+ **On failure:**
2390
+
2391
+ ```http
2392
+ HTTP/1.1 400
2393
+
2394
+ {
2395
+ "ErrorCode": 400,
2396
+ "ErrorMessage": "SUBMISSION_REQUIRED",
2397
+ "Data": null,
2398
+ "Succeeded": false
2399
+ }
2400
+ ```
2401
+
2402
+ #### `fileDownloadAPIEndpoint` `GET` API Request and Response
2403
+ This API is used to download the file uploaded by the user. It returns the file data in Base64.
2404
+
2405
+ **Request:**
2406
+
2407
+ - **HTTP Method**: GET
2408
+ - **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`:
2409
+ - `referenceValue` is the `referenceValue` of the file current temporary saved instance (see more [💾 Temporary save feature](#-temporary-save-feature)).
2410
+ - `fileid` is the `fileId` of the file uploaded by the user.
2411
+ - `sha256` is the `sha256` of the file uploaded by the user.
2412
+ - **Headers**:
2413
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2414
+ - **client-key**: `<clientKey>` (from config/env)
2415
+ - **service-id**: `<serviceId>` (from config/env)
2416
+ - **Accept**: `text/plain`
2417
+
2418
+ **Example Request:**
2419
+
2420
+ ```http
2421
+ GET fileDownload/1234567890/123456789123456/12345678901234567890123 HTTP/1.1
2422
+ Host: localhost:3002
2423
+ Authorization: Bearer eyJhbGciOi...
2424
+ client-key: 12345678901234567890123456789000
2425
+ service-id: 123
2426
+ Accept: text/plain
2427
+ Content-Type: application/json
2428
+ ```
2429
+
2430
+ **Response:**
2431
+
2432
+ The API is expected to return a JSON response with the following structure:
2433
+
2434
+ **When file is found:**
2435
+
2436
+ ```http
2437
+ HTTP/1.1 200 OK
2438
+
2439
+ {
2440
+ "ErrorCode": 0,
2441
+ "ErrorMessage": null,
2442
+ "Data": {
2443
+ "fileId": "123456789123456",
2444
+ "fileName": "passport.pdf",
2445
+ "contentType": "application/pdf",
2446
+ "fileSize": 1872,
2447
+ "sha256": "12345678901234567890123456789012345678901234567890123456789012345",
2448
+ "base64": "JVBERi0xLjMKJZOMi54gUm....
2449
+ ",
2450
+ "description": null,
2451
+ "uid": null,
2452
+ "tag": "Passport"
2453
+ },
2454
+ "Succeeded": true
2455
+ }
2456
+ ```
2457
+
2458
+ **When file is NOT found:**
2459
+
2460
+ ```http
2461
+ HTTP/1.1 404 Not Found
2462
+
2463
+ {
2464
+ "ErrorCode": 404,
2465
+ "ErrorMessage": "File not found",
2466
+ "InformationMessage": null,
2467
+ "Data": null,
2468
+ "Succeeded": false
2469
+ }
2470
+ ```
2471
+
2472
+ #### `fileDeleteAPIEndpoint` `DELETE` API Request and Response
2473
+ This API is used to delete the file uploaded by the user. It returns the file data in Base64.
2474
+ > ⚠️ **Important note:**
2475
+ > 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.
2476
+
2477
+ **Request:**
2478
+
2479
+ - **HTTP Method**: DELETE
2480
+ - **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`:
2481
+ - `fileid` is the `fileId` of the file uploaded by the user.
2482
+ - `sha256` is the `sha256` of the file uploaded by the user.
2483
+ - **Headers**:
2484
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2485
+ - **client-key**: `<clientKey>` (from config/env)
2486
+ - **service-id**: `<serviceId>` (from config/env)
2487
+ - **Accept**: `text/plain`
2488
+
2489
+ **Example Request:**
2490
+
2491
+ ```http
2492
+ DELETE fileDelete/123456789123456/12345678901234567890123 HTTP/1.1
2493
+ Host: localhost:3002
2494
+ Authorization: Bearer eyJhbGciOi...
2495
+ client-key: 12345678901234567890123456789000
2496
+ service-id: 123
2497
+ Accept: text/plain
2498
+ Content-Type: application/json
2499
+ ```
2500
+
2501
+ **Response:**
2502
+
2503
+ The API is expected to return a JSON response with the following structure:
2504
+
2505
+ **When file is found and deleted:**
2506
+
2507
+ ```http
2508
+ HTTP/1.1 200 OK
2509
+
2510
+ {
2511
+ "ErrorCode": 0,
2512
+ "ErrorMessage": null,
2513
+ "Succeeded": true
2514
+ }
2515
+ ```
2516
+
2517
+ **When file is NOT found:**
2518
+
2519
+ ```http
2520
+ HTTP/1.1 404 Not Found
2521
+
2522
+ {
2523
+ "ErrorCode": 404,
2524
+ "ErrorMessage": "File not found",
2525
+ "InformationMessage": null,
2526
+ "Data": null,
2527
+ "Succeeded": false
2528
+ }
2529
+ ```
2530
+
2531
+ #### File uploads in the data layer
2532
+ 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:
2533
+
2534
+ ```json
2535
+ {
2536
+ "fileId": "1234567891234567890",
2537
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
2538
+ }
2539
+ ```
2540
+
2541
+ #### File uploads when submitted
2542
+ 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`.
2543
+
2544
+ 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:
2545
+
2546
+ ```json
2547
+ {
2548
+ "passportAttachment": {
2549
+ "fileId": "1234567891234567890",
2550
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
2551
+ }
2552
+ }
2553
+ ```
2554
+
2555
+ #### File uploads backward compatibility
2556
+ If these endpoints are not defined in the service JSON, the file upload logic is skipped entirely.
2557
+ Existing services will continue to work without modification.
2558
+
1611
2559
  ### 🛣️ Routes
1612
2560
  The project uses express.js to serve the following routes:
1613
2561
 
@@ -1616,16 +2564,18 @@ The project uses express.js to serve the following routes:
1616
2564
  - **`/: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
2565
  - **`/: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
2566
  - **`/: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.
2567
+ - **`/:siteId/:pageUrl/view-file/:elementName`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the specified file in a new tab.
2568
+ - **`/:siteId/:pageUrl/delete-file/:elementName`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the delete confirmation page and handles the file delete.
1620
2569
 
1621
2570
  #### Authentication routes:
1622
2571
  - **`/signin-oidc`**: CY Login authentication endpoint.
1623
2572
  - **`/login`**: Redirect to CY Login login page.
1624
2573
  - **`/logout`**: CY Login logout endpoint.
1625
2574
 
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.
2575
+ #### API routes:
2576
+ - **`/apis/:siteId/:pageUrl/upload`**: Uploads a file. Used from the client side JS.
1627
2577
 
1628
- ### 👨‍💻 Enviromental variables
2578
+ ### 👨‍💻 Environment variables
1629
2579
  The environment variables are defined in:
1630
2580
  - **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
2581
  - **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 +2646,14 @@ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
1696
2646
  TEST_SUBMISSION_API_SERVIVE_ID=123
1697
2647
  TEST_SUBMISSION_DSF_GTW_KEY=12345678901234567890123456789000
1698
2648
 
1699
- # Optional Temporary Save GET and PUT endpoint (test service)
2649
+ # Optional Temporary Save GET and PUT endpoints (test service)
1700
2650
  TEST_SUBMISSION_GET_API_URL=http://localhost:3002/getTempSubmission
1701
2651
  TEST_SUBMISSION_PUT_API_URL=http://localhost:3002/save
1702
2652
 
2653
+ # Optional File Upload and download endpoints (test service)
2654
+ TEST_FILE_UPLOAD_API_URL=http://localhost:3002/fileUpload
2655
+ TEST_FILE_DOWNLOAD_API_URL=http://localhost:3002/fileDownload
2656
+ TEST_FILE_DELETE_API_URL=http://localhost:3002/fileDelete
1703
2657
 
1704
2658
  # Eligibility checks (optional test APIs)
1705
2659
  TEST_ELIGIBILITY_1_API_URL=http://localhost:3002/eligibility1