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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +861 -7
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -32,7 +32,8 @@ The project is designed to support the [Linear structure](https://gov-cy.github.
32
32
  - [📤 Site submissions](#-site-submissions)
33
33
  - [✅ Input validations](#-input-validations)
34
34
  - [✅ Conditional logic](#-conditional-logic)
35
- - [💾 Temporary save](#-temporary-save)
35
+ - [💾 Temporary save feature](#-temporary-save-feature)
36
+ - [🗃️ Files uploads feature](#%EF%B8%8F-files-uploads-feature)
36
37
  - [🛣️ Routes](#%EF%B8%8F-routes)
37
38
  - [👨‍💻 Enviromental variables](#-enviromental-variables)
38
39
  - [🔒 Security note](#-security-note)
@@ -140,6 +141,538 @@ The CY Login settings are configured in the `secrets/.env` file.
140
141
  ### 🧩 Dynamic Services Rendering
141
142
  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
143
 
144
+ Here is an example JSON config:
145
+
146
+ ```json
147
+ {
148
+ "site": {
149
+ "id": "test",
150
+ "lang": "el", //<-- Default language
151
+ "languages": [ //<-- Supported languages
152
+ {
153
+ "code": "el",
154
+ "label": "EL",
155
+ "alt": "Ελληνική γλώσσα",
156
+ "href": "?lang=el"
157
+ },
158
+ {
159
+ "code": "en",
160
+ "label": "EN",
161
+ "alt": "English language",
162
+ "href": "?lang=en"
163
+ }
164
+ ],
165
+ "footerLinks": [ //<-- Links on the footer
166
+ {
167
+ "label": {
168
+ "el": "Δήλωση απορρήτου",
169
+ "en": "Privacy statement",
170
+ "tr": "Privacy statement"
171
+ },
172
+ "href": "test/privacy-statement"
173
+ },
174
+ {
175
+ "label": {
176
+ "el": "Cookies",
177
+ "en": "Cookies",
178
+ "tr": "Cookies"
179
+ },
180
+ "href": "test/cookie-policy"
181
+ },
182
+ {
183
+ "label": {
184
+ "el": "Προσβασιμότητα",
185
+ "en": "Accessibility",
186
+ "tr": "Accessibility"
187
+ },
188
+ "href": "test/accessibility-statement"
189
+ }
190
+ ],
191
+ "footerIcons": [ //<-- Icons on the footer
192
+ {
193
+ "target": "_blank",
194
+ "src": {
195
+ "el": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/FundedbyEU_NextGeneration_H53-EL.png",
196
+ "en": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/FundedbyEU_NextGeneration_H53-EN.png",
197
+ "tr": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/FundedbyEU_NextGeneration_H53-EN.png"
198
+ },
199
+ "alt": {
200
+ "el": "Χρηματοδοτείται από την ΕΕ Next Generation EU",
201
+ "en": "Funded by the EU Next Generation EU",
202
+ "tr": "Funded by the EU Next Generation EU"
203
+ },
204
+ "href": {
205
+ "el": "https://europa.eu/",
206
+ "en": "https://europa.eu/",
207
+ "tr": "https://europa.eu/"
208
+ },
209
+ "title": {
210
+ "el": "Μετάβαση στην ιστοσελίδα της ΕΕ",
211
+ "en": "Go to EU website",
212
+ "tr": "Go to EU website"
213
+ }
214
+ },
215
+ {
216
+ "target": "_blank",
217
+ "src": {
218
+ "el": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/CYpros%20to%20aurio%20logo%20eng_H53_EL.png",
219
+ "en": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/CYpros%20to%20aurio%20logo%20eng_H53_EN.png",
220
+ "tr": "https://cdn.jsdelivr.net/gh/gov-cy/govdesign@main/CYpros%20to%20aurio%20logo%20eng_H53_EN.png"
221
+ },
222
+ "alt": {
223
+ "el": "Κύπρος το Αύριο, σχέδιο ανάκαμψης και ανθεντικότητας",
224
+ "en": "Cyprus tomorrow, recovery and resilience plan",
225
+ "tr": "Cyprus tomorrow, recovery and resilience plan"
226
+ },
227
+ "href": {
228
+ "el": "http://www.cyprus-tomorrow.gov.cy/",
229
+ "en": "http://www.cyprus-tomorrow.gov.cy/",
230
+ "tr": "http://www.cyprus-tomorrow.gov.cy/"
231
+ },
232
+ "title": {
233
+ "el": "Μετάβαση στην ιστοσελίδα Κύπρος το Αύριο",
234
+ "en": "Go to Cyprus Tomorrow website",
235
+ "tr": "Go to Cyprus Tomorrow website"
236
+ }
237
+ }
238
+ ],
239
+ "menu": { //<-- Menu altext
240
+ "el": "Μενού",
241
+ "en": "Menu",
242
+ "tr": "Menu"
243
+ },
244
+ "title": { //<-- Service title (meta)
245
+ "el": "Υπηρεσία τεστ",
246
+ "en": "Test service",
247
+ "tr": ""
248
+ },
249
+ "headerTitle": { //<-- Service title (as it apears in the header)
250
+ "el": "[Το ΟΝΟΜΑ της υπηρεσίας που θα φαίνεται στις φόρμες]",
251
+ "en": "[The NAME of the service as it will appear on forms]",
252
+ "tr": ""
253
+ },
254
+ "description": { //<-- Service description (meta)
255
+ "el": "[Υποβάλετε αίτηση για ...]",
256
+ "en": "[Submit an application ...]",
257
+ "tr": ""
258
+ },
259
+ "url": "https://gov.cy", //<-- URL in (meta, for example `og:url`)
260
+ "cdn": { //<-- CDN URL and integrity
261
+ "dist": "https://cdn.jsdelivr.net/gh/gov-cy/govcy-design-system@3.2.0/dist",
262
+ "cssIntegrity": "sha384-qjx16YXHG+Vq/NVtwU2aDTc7DoLOyaVNuOHrwA3aTrckpM/ycxZoR5dx7ezNJ/Lv",
263
+ "jsIntegrity": "sha384-tqEyCdi3GS4uDXctplAd7ODjiK5fo2Xlqv65e8w/cVvrcBf89tsxXFHXXNiUDyM7"
264
+ },
265
+ "submission_data_version": "1", //<-- Submission data version
266
+ "renderer_version": "1.16.1", //<-- govcy-frontend-renderer version
267
+ "design_systems_version": "3.2.0", //<-- govcy-design-system version
268
+ "homeRedirectPage": { //<-- Home redirect page
269
+ "el": "https://www.gov.cy/service/aitisi-gia-taftotita/",
270
+ "en": "https://www.gov.cy/en/service/issue-an-id-card/",
271
+ "tr": "https://www.gov.cy/en/service/issue-an-id-card/"
272
+ },
273
+ "copyrightText": { //<-- Copyright text
274
+ "el": "Κυπριακή Δημοκρατία, 2025",
275
+ "en": "Republic of Cyprus, 2025",
276
+ "tr": "Republic of Cyprus, 2025"
277
+ },
278
+ "submissionAPIEndpoint": { //<-- Submission API endpoint
279
+ "url": "TEST_SUBMISSION_API_URL",
280
+ "method": "POST",
281
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
282
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
283
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY",
284
+ "response": {
285
+ "errorResponse": {
286
+ "102": {
287
+ "error": "user not administrator",
288
+ "page": "/test/user-not-admin"
289
+ },
290
+ "105": {
291
+ "error": "user not registration",
292
+ "page": "/test/user-not-registered"
293
+ }
294
+ }
295
+ }
296
+ },
297
+ "submissionGetAPIEndpoint": { //<-- Submission GET API endpoint for temporary saving
298
+ "url": "TEST_SUBMISSION_GET_API_URL",
299
+ "method": "GET",
300
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
301
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
302
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
303
+ },
304
+ "submissionPutAPIEndpoint": { //<-- Submission PUT API endpoint for temporary saving
305
+ "url": "TEST_SUBMISSION_PUT_API_URL",
306
+ "method": "PUT",
307
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
308
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
309
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
310
+ },
311
+ "fileUploadAPIEndpoint": { //<-- File upload API endpoint
312
+ "url": "TEST_UPLOAD_FILE_API_URL",
313
+ "method": "POST",
314
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
315
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
316
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
317
+ },
318
+ "fileDownloadAPIEndpoint": { //<-- File download API endpoint
319
+ "url": "TEST_DOWNLOAD_FILE_API_URL",
320
+ "method": "GET",
321
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
322
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
323
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY"
324
+ },
325
+ "eligibilityAPIEndpoints": [ //<-- Eligibility API endpoints
326
+ {
327
+ "url": "TEST_ELIGIBILITY_2_API_URL",
328
+ "method": "GET",
329
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
330
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
331
+ "dsfgtwApiKey": "TEST_SUBMISSION_DSF_GTW_KEY",
332
+ "cashingTimeoutMinutes": "60",
333
+ "params": {},
334
+ "response": {
335
+ "errorResponse": {
336
+ "105": {
337
+ "error": "user not registration",
338
+ "page": "/test/user-not-registered"
339
+ }
340
+ }
341
+ }
342
+ }
343
+ ]
344
+ },
345
+ "pages": [ //<-- Pages
346
+ {
347
+ "pageData": { //<-- 1st Page's data (form)
348
+ "url": "index", // Page URL
349
+ "title": { // Page title
350
+ "el": "Επιλογή Εγγάφου",
351
+ "en": "Document selection",
352
+ "tr": ""
353
+ },
354
+ "layout": "layouts/govcyBase.njk", // Page layout
355
+ "mainLayout": "two-third", // Page main layout
356
+ "nextPage": "data-entry-radios" // The next page's URL
357
+ },
358
+ "pageTemplate": { //<-- Page template
359
+ "sections": [ //<-- Page sections
360
+ {
361
+ "name": "main", //<-- Main section
362
+ "elements": [ //<-- Main section elements
363
+ {
364
+ "element": "form", // Form element
365
+ "params": {
366
+ "elements": [ // Elements inside the form
367
+ {
368
+ "element": "checkboxes", // Checkboxes element
369
+ "params": { // Checkboxes parameters
370
+ "id": "certificate_select",
371
+ "name": "certificate_select",
372
+ "legend": {
373
+ "el": "Τι έγγραφα επιθυμείτε να εκδώσετε;",
374
+ "en": "What documents do you wish to issue?"
375
+ },
376
+ "items": [
377
+ {
378
+ "value": "birth",
379
+ "text": {
380
+ "el": "Πιστοποιητικό γέννησης​",
381
+ "en": "Birth certificate",
382
+ "tr": ""
383
+ },
384
+ "hint": {
385
+ "el": "Αν η γέννηση έγινε στην Κύπρο ή στο εξωτερικό και έχει ενημερωθεί το μητρώο του Αρχείου Πληθυσμού ",
386
+ "en": "For a birth in Cyprus or abroad which Civil Registry is updated with "
387
+ }
388
+ },
389
+ {
390
+ "value": "permanent_residence",
391
+ "text": {
392
+ "el": "Βεβαίωση μόνιμης διαμονής​",
393
+ "en": "Certificate of permanent residence",
394
+ "tr": ""
395
+ },
396
+ "hint": {
397
+ "el": "Για όσους είναι εγγεγραμμένοι στον εκλογικό κατάλογο",
398
+ "en": "For those registered in the electoral list"
399
+ }
400
+ },
401
+ {
402
+ "value": "student_proof_of_origin",
403
+ "text": {
404
+ "el": "Βεβαίωση καταγωγής",
405
+ "en": "Certificate of origin",
406
+ "tr": ""
407
+ },
408
+ "hint": {
409
+ "el": "Για αίτηση σε πανεπιστήμια στην Ελλάδα",
410
+ "en": "To apply to a university in Greece"
411
+ }
412
+ }
413
+ ],
414
+ "isPageHeading": true,
415
+ "hint": {
416
+ "el": "Επιλέξτε ένα ή περισσότερα έγγραφα",
417
+ "en": "Select one or more documents",
418
+ "tr": ""
419
+ }
420
+ },
421
+ "validations": [ // Checkboxes validations
422
+ {
423
+ "check": "required",
424
+ "params": {
425
+ "checkValue": "",
426
+ "message": {
427
+ "el": "Επιλέξετε ένα ή περισσότερα έγγραφα",
428
+ "en": "Select one or more documents",
429
+ "tr": ""
430
+ }
431
+ }
432
+ }
433
+ ]
434
+ },
435
+ {
436
+ "element": "button",
437
+ "params": {
438
+ "id": "continue",
439
+ "variant": "primary",
440
+ "text": {
441
+ "el": "Συνέχεια",
442
+ "en": "Continue"
443
+ }
444
+ }
445
+ }
446
+ ]
447
+ }
448
+ }
449
+ ]
450
+ }
451
+ ]
452
+ }
453
+ },
454
+ {
455
+ "pageData": { //<-- 2nd Page's data (form)
456
+ "url": "data-entry-radios",
457
+ "title": {
458
+ "el": "Στοιχεία επικοινωνίας ",
459
+ "en": "Contact details",
460
+ "tr": ""
461
+ },
462
+ "layout": "layouts/govcyBase.njk",
463
+ "mainLayout": "two-third",
464
+ "nextPage": "review"
465
+ },
466
+ "pageTemplate": {
467
+ "sections": [
468
+ {
469
+ "name": "beforeMain",
470
+ "elements": [
471
+ {
472
+ "element": "backLink",
473
+ "params": {}
474
+ }
475
+ ]
476
+ },
477
+ {
478
+ "name": "main",
479
+ "elements": [
480
+ {
481
+ "element": "form",
482
+ "params": {
483
+ "elements": [
484
+ {
485
+ "element": "radios",
486
+ "params": {
487
+ "id": "mobile_select",
488
+ "name": "mobile_select",
489
+ "legend": {
490
+ "el": "Σε ποιο κινητό μπορούμε να επικοινωνήσουμε μαζί σας;",
491
+ "en": "What mobile number can we use to contact you?"
492
+ },
493
+ "items": [
494
+ {
495
+ "value": "mobile",
496
+ "text": {
497
+ "el": "Στο [99 123456]",
498
+ "en": "You can use [99 123456]",
499
+ "tr": ""
500
+ }
501
+ },
502
+ {
503
+ "value": "other",
504
+ "text": {
505
+ "el": "Θα δώσω άλλο αριθμό",
506
+ "en": "I will give a different number",
507
+ "tr": ""
508
+ },
509
+ "conditionalElements": [
510
+ {
511
+ "element": "fileInput",
512
+ "params": {
513
+ "id": "proof",
514
+ "name": "proof",
515
+ "label": {
516
+ "el": "Αποδεικτικό τηλεφώνου",
517
+ "en": "Telephone proof",
518
+ "tr": ""
519
+ },
520
+ "isPageHeading": false,
521
+ "hint": {
522
+ "el": "PDF, JPG, JPEG, PNG, είναι οι αποδεκτές μορφές",
523
+ "en": "PDF, JPG, JPEG, PNG are the acceptable formats",
524
+ "tr": ""
525
+ }
526
+ },
527
+ "validations": [
528
+ {
529
+ "check": "required",
530
+ "params": {
531
+ "checkValue": "",
532
+ "message": {
533
+ "el": "Ανεβάστε τον αποδεικτικό τηλεφώνου",
534
+ "en": "Upload the telephone proof",
535
+ "tr": ""
536
+ }
537
+ }
538
+ }
539
+ ]
540
+ }
541
+ ]
542
+ }
543
+ ],
544
+ "isPageHeading": true
545
+ },
546
+ "validations": [
547
+ {
548
+ "check": "required",
549
+ "params": {
550
+ "checkValue": "",
551
+ "message": {
552
+ "el": "Επιλέξετε αν θέλετε να χρησιμοποιήσετε το τηλέφωνο που φαίνεται εδώ, ή κάποιο άλλο",
553
+ "en": "Choose if you'd like to use the phone number shown here, or a different one",
554
+ "tr": ""
555
+ }
556
+ }
557
+ }
558
+ ]
559
+ },
560
+ {
561
+ "element": "button",
562
+ "params": {
563
+ "id": "continue",
564
+ "variant": "primary",
565
+ "text": {
566
+ "el": "Συνέχεια",
567
+ "en": "Continue"
568
+ }
569
+ }
570
+ }
571
+ ]
572
+ }
573
+ }
574
+ ]
575
+ }
576
+ ]
577
+ }
578
+ },
579
+ {
580
+ "pageData": { //<-- 3rd Page's data (not a form)
581
+ "url": "user-not-registered",
582
+ "title": {
583
+ "el": "Δεν είστε εγγεγραμμένοι ",
584
+ "en": "You are not an registered",
585
+ "tr": ""
586
+ },
587
+ "layout": "layouts/govcyBase.njk",
588
+ "mainLayout": "two-third"
589
+ },
590
+ "pageTemplate": {
591
+ "sections": [
592
+ {
593
+ "name": "beforeMain",
594
+ "elements": []
595
+ },
596
+ {
597
+ "name": "main",
598
+ "elements": [
599
+ {
600
+ "element": "textElement",
601
+ "params": {
602
+ "id": "title",
603
+ "type": "h1",
604
+ "text": {
605
+ "el": "Δεν είστε εγγεγραμμένοι",
606
+ "en": "You are not registered"
607
+ }
608
+ }
609
+ },
610
+ {
611
+ "element": "htmlElement",
612
+ "params": {
613
+ "id": "body",
614
+ "text": {
615
+ "el": "<p>Για να υποβάλετε σε υπηρεσία αυτή, χρειάζεται να είστε εγγεγραμμένοι στο ΧΥΖ.</p>",
616
+ "en": "<p>To submit in this service you need to be registered at XYZ.</p>"
617
+ }
618
+ }
619
+ }
620
+ ]
621
+ }
622
+ ]
623
+ }
624
+ },
625
+ {
626
+ "pageData": { //<-- 4th Page's data (not a form)
627
+ "url": "user-not-admin",
628
+ "title": {
629
+ "el": "Δεν είστε διαχειριστής ",
630
+ "en": "You are not an administrator",
631
+ "tr": ""
632
+ },
633
+ "layout": "layouts/govcyBase.njk",
634
+ "mainLayout": "two-third"
635
+ },
636
+ "pageTemplate": {
637
+ "sections": [
638
+ {
639
+ "name": "beforeMain",
640
+ "elements": []
641
+ },
642
+ {
643
+ "name": "main",
644
+ "elements": [
645
+ {
646
+ "element": "textElement",
647
+ "params": {
648
+ "id": "title",
649
+ "type": "h1",
650
+ "text": {
651
+ "el": "Δεν είστε διαχειριστής ",
652
+ "en": "You are not an administrator"
653
+ }
654
+ }
655
+ },
656
+ {
657
+ "element": "htmlElement",
658
+ "params": {
659
+ "id": "body",
660
+ "text": {
661
+ "el": "<p>Για να υποβάλετε σε υπηρεσία αυτή, χρειάζεται να είστε διαχειριστής στο ΧΥΖ.</p>",
662
+ "en": "<p>To submit in this service you need to be an administrator of XYZ.</p>"
663
+ }
664
+ }
665
+ }
666
+ ]
667
+ }
668
+ ]
669
+ }
670
+ }
671
+ ]
672
+ }
673
+
674
+ ```
675
+
143
676
  Here are some details explaining the JSON structure:
144
677
 
145
678
  - `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 +688,13 @@ Here are some details explaining the JSON structure:
155
688
  ```
156
689
  - `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
690
  - `submissionAPIEndpoint`: The submission API endpoint, to be used for submitting the form. See more on the [Submission API Endoint](#-site-submissions) section below.
691
+ - `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.
692
+ - `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.
693
+ - `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.
694
+ - `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.
158
695
  - `pages` array: An array of page objects, each representing a page in the site.
159
696
  - `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
697
+ - `nextPage`: The URL of the next page to be rendered after the user clicks the `continue` button.
160
698
  - `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
699
  - `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
700
 
@@ -297,14 +835,24 @@ Lets break down the JSON config for this page:
297
835
  - `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
836
  - `pageData.conditions` is the array that defines the [conditional logic](#-conditional-logic)
299
837
  - **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.
838
+ - `sections` is an array of sections, which is an array of elements. Sections allowed: `beforeMain`, `main`, `afterMain`.
839
+ - `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
840
 
301
841
  **Forms vs static content**
302
842
 
303
843
  - If the `pageTemplate` includes a `form` element in the `main` section and `button` element, the system will treat it as form and will:
304
844
  - Perform the eligibility checks
305
845
  - Display the form
306
- - Collect the form data
307
- - Validate the form data
846
+ - 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)):
847
+ - `textInput`
848
+ - `textArea`
849
+ - `select`
850
+ - `radios`
851
+ - `checkboxes`
852
+ - `datePicker`
853
+ - `dateInput`
854
+ - `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)
855
+ - Validate the form data (see more on the [Input validations](#-input-validations) section below)
308
856
  - Store the form data in the systems data layer
309
857
  - Redirect the user to the next page (or `review` page if the user came from the review page)
310
858
  - Else if the `pageTemplate` does not include a `form` element in the `main` section, the system will treat it as static content and will:
@@ -1422,12 +1970,12 @@ Explanation:
1422
1970
  - `[].concat(...)`: safely flattens a string or array into an array.
1423
1971
  - `.includes('value1')`: checks if the value is selected.
1424
1972
 
1425
- ### 💾 Temporary save
1973
+ ### 💾 Temporary save feature
1426
1974
 
1427
1975
  The **temporary save** feature allows user progress to be stored in an external API and automatically reloaded on the next visit.
1428
1976
  This is useful for long forms or cases where users may leave and return later.
1429
1977
 
1430
- #### How to configure temporary save
1978
+ #### How to enable and configure temporary save
1431
1979
  To use this feature, configure the config JSON file. In your service’s `site` object, add both a `submissionGetAPIEndpoint` and `submissionPutAPIEndpoint` entry:
1432
1980
 
1433
1981
  ```json
@@ -1608,6 +2156,310 @@ HTTP/1.1 401 Unauthorized
1608
2156
  If these endpoints are not defined in the service JSON, the temporary save/load logic is skipped entirely.
1609
2157
  Existing services will continue to work without modification.
1610
2158
 
2159
+ ### 🗃️ Files uploads feature
2160
+
2161
+ 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.
2162
+
2163
+ #### Pre-requisites for the files uploads feature
2164
+ The [💾 Temporary save feature](#-temporary-save-feature) must be enabled for the file uploads feature to work.
2165
+
2166
+
2167
+ #### How to enable and configure file uploads
2168
+ To use this feature, configure the config JSON file. In your service’s `site` object, add both a `fileUploadAPIEndpoint` and `fileDownloadAPIEndpoint` entry:
2169
+
2170
+ ```json
2171
+ "fileUploadAPIEndpoint": {
2172
+ "url": "TEST_UPLOAD_FILE_API_URL",
2173
+ "method": "POST",
2174
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2175
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2176
+ },
2177
+ "fileDownloadAPIEndpoint": {
2178
+ "url": "TEST_DOWNLOAD_FILE_API_URL",
2179
+ "method": "GET",
2180
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
2181
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID"
2182
+ }
2183
+ ```
2184
+
2185
+ These values should point to environment variables that hold your real endpoint URLs and credentials.
2186
+
2187
+ In your `secrets/.env` file (and staging/production configs), define the variables referenced above:
2188
+
2189
+ ```dotenv
2190
+ TEST_UPLOAD_FILE_API_URL=https://example.com/api/fileUpload
2191
+ TEST_DOWNLOAD_FILE_API_URL=https://example.com/api/fileDownload
2192
+ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
2193
+ TEST_SUBMISSION_API_SERVICE_ID=123
2194
+ ```
2195
+
2196
+
2197
+ #### How to define the file input field in the JSON config
2198
+ 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.
2199
+
2200
+ 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))
2201
+
2202
+ Here is a sample code section of a page definition with a file input field:
2203
+
2204
+ ```json
2205
+ {
2206
+ "pageData": {
2207
+ "url": "data-entry-file",
2208
+ "title": {
2209
+ "el": "Λογαριασμός κινήσεως",
2210
+ "en": "Utility bill"
2211
+ },
2212
+ "layout": "layouts/govcyBase.njk",
2213
+ "mainLayout": "two-third",
2214
+ "nextPage": "data-entry-all"
2215
+ },
2216
+ "pageTemplate": {
2217
+ "sections": [
2218
+ {
2219
+ "name": "beforeMain",
2220
+ "elements": [
2221
+ {
2222
+ "element": "backLink",
2223
+ "params": {}
2224
+ }
2225
+ ]
2226
+ },
2227
+ {
2228
+ "name": "main",
2229
+ "elements": [
2230
+ {
2231
+ "element": "form",
2232
+ "params": {
2233
+ "elements": [
2234
+ {
2235
+ "element": "fileInput", //<-- this is the file input
2236
+ "params": {
2237
+ "id": "utility",
2238
+ "name": "utility",
2239
+ "label": {
2240
+ "el": "Λογαριασμός κινήσεως",
2241
+ "en": "Utility bill"
2242
+ },
2243
+ "isPageHeading": true,
2244
+ "hint": {
2245
+ "el": "PDF, JPG, JPEG, PNG, είναι οι αποδεκτές μορφές",
2246
+ "en": "PDF, JPG, JPEG, PNG are the acceptable formats"
2247
+ }
2248
+ },
2249
+ "validations": [ //<-- this is the file input validations
2250
+ {
2251
+ "check": "required",
2252
+ "params": {
2253
+ "checkValue": "",
2254
+ "message": {
2255
+ "el": "Ανεβάστε τον λογαριασμό κινήσεως",
2256
+ "en": "Upload the utility bill"
2257
+ }
2258
+ }
2259
+ }
2260
+ ]
2261
+ },
2262
+ {
2263
+ "element": "button",
2264
+ "params": {
2265
+ "id": "continue",
2266
+ "variant": "primary",
2267
+ "text": {
2268
+ "el": "Συνέχεια",
2269
+ "en": "Continue"
2270
+ }
2271
+ }
2272
+ }
2273
+ ]
2274
+ }
2275
+ }
2276
+ ]
2277
+ }
2278
+ ]
2279
+ }
2280
+ }
2281
+ ```
2282
+
2283
+ #### How file uploads works
2284
+
2285
+ - **On a form page with an upload input field**:
2286
+ - **On first load**: the page displays the fileInput field to choose a file.
2287
+ - **On choosing a file**: the file is uploaded to the `fileUploadAPIEndpoint` endpoint.
2288
+ - The `fileUploadAPIEndpoint` validates the input for allowed file types and file size. On validation error an error message is displayed.
2289
+ - 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.
2290
+ - **On a form page after upload**:
2291
+ - The `fileView` element is displayed with links to `View` or `Delete` the file.
2292
+ - **When clinking `view`**:
2293
+ - The file is downloaded using the `fileDownloadAPIEndpoint` and opened in a new tab.
2294
+ - **When clinking `delete`**:
2295
+ - A confimation page is displayed asking the user to confirm the deletion. If the user confirms, the file is deleted from the data layer and the `fileView` element is removed from the page.
2296
+ - **On the `review` page after upload**:
2297
+ - The element is displayed with a link to `View file`. A `Change` link is also displayed for the whole page.
2298
+ - **On the `success` page and email after upload**:
2299
+ - The element is displayed with links a marking `File uploaded`.
2300
+
2301
+
2302
+ #### `fileUploadAPIEndpoint` `POST` API Request and Response
2303
+ This API is used to temporarily store the file uploaded by the user.
2304
+
2305
+ **Request:**
2306
+
2307
+ - **HTTP Method**: POST
2308
+ - **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`
2309
+ - **Headers**:
2310
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2311
+ - **client-key**: `<clientKey>` (from config/env)
2312
+ - **service-id**: `<serviceId>` (from config/env)
2313
+ - **Accept**: `application/json`
2314
+ - **Content-Type**: `multipart/form-data` (typically automatically set when using FormData in the browser)
2315
+ - **Body (multipart/form-data)**: The body the actual file to be uploaded (PDF, JPEG, etc.)
2316
+
2317
+ **Example Request:**
2318
+
2319
+ ``` bash
2320
+ curl --location 'https://example.gov.cy/api/v1/files/upload/passport' \
2321
+ --header 'client-key: 12345678901234567890123456789000' \
2322
+ --header 'service-id: 123' \
2323
+ --header 'Authorization: Bearer eyJhbGciOi...' \
2324
+ --form 'file=@"/path/to/file.pdf"'
2325
+ ```
2326
+
2327
+ **Response:**
2328
+
2329
+ The API is expected to return a JSON response with the following structure:
2330
+
2331
+ **On success:**
2332
+
2333
+ ```http
2334
+ HTTP/1.1 200 OK
2335
+
2336
+ {
2337
+ "ErrorCode": 0,
2338
+ "ErrorMessage": null,
2339
+ "Data": {
2340
+ "fileId": "6899adac8864bf90a90047c3",
2341
+ "fileName": "passport.pdf",
2342
+ "contentType": "application/pdf",
2343
+ "fileSize": 4721123,
2344
+ "sha256": "8adb79e0e782280dad8beb227333a21796b8e01d019ab1e84cfea89a523b0e7d",
2345
+ "description": "passport.pdf",
2346
+ "tag": "passport"
2347
+ },
2348
+ "Succeeded": true
2349
+ }
2350
+ ```
2351
+
2352
+ **On failure:**
2353
+
2354
+ ```http
2355
+ HTTP/1.1 400
2356
+
2357
+ {
2358
+ "ErrorCode": 400,
2359
+ "ErrorMessage": "SUBMISSION_REQUIRED",
2360
+ "Data": null,
2361
+ "Succeeded": false
2362
+ }
2363
+ ```
2364
+
2365
+ #### `fileDownloadAPIEndpoint` `GET` API Request and Response
2366
+ This API is used to download the file uploaded by the user. It returns the file data in Base64.
2367
+
2368
+ **Request:**
2369
+
2370
+ - **HTTP Method**: GET
2371
+ - **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`:
2372
+ - `referenceValue` is the `referenceValue` of the file current temporary saved instance (see more [💾 Temporary save feature](#-temporary-save-feature)).
2373
+ - `fileid` is the `fileId` of the file uploaded by the user.
2374
+ - `sha256` is the `sha256` of the file uploaded by the user.
2375
+ - **Headers**:
2376
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
2377
+ - **client-key**: `<clientKey>` (from config/env)
2378
+ - **service-id**: `<serviceId>` (from config/env)
2379
+ - **Accept**: `text/plain`
2380
+
2381
+ **Example Request:**
2382
+
2383
+ ```http
2384
+ GET fileDownload/1234567890/123456789123456/12345678901234567890123 HTTP/1.1
2385
+ Host: localhost:3002
2386
+ Authorization: Bearer eyJhbGciOi...
2387
+ client-key: 12345678901234567890123456789000
2388
+ service-id: 123
2389
+ Accept: text/plain
2390
+ Content-Type: application/json
2391
+ ```
2392
+
2393
+ **Response:**
2394
+
2395
+ The API is expected to return a JSON response with the following structure:
2396
+
2397
+ **When file is found:**
2398
+
2399
+ ```http
2400
+ HTTP/1.1 200 OK
2401
+
2402
+ {
2403
+ "ErrorCode": 0,
2404
+ "ErrorMessage": null,
2405
+ "Data": {
2406
+ "fileId": "123456789123456",
2407
+ "fileName": "passport.pdf",
2408
+ "contentType": "application/pdf",
2409
+ "fileSize": 1872,
2410
+ "sha256": "12345678901234567890123456789012345678901234567890123456789012345",
2411
+ "base64": "JVBERi0xLjMKJZOMi54gUm....
2412
+ ",
2413
+ "description": null,
2414
+ "uid": null,
2415
+ "tag": "Passport"
2416
+ },
2417
+ "Succeeded": true
2418
+ }
2419
+ ```
2420
+
2421
+ **When file is NOT found:**
2422
+
2423
+ ```http
2424
+ HTTP/1.1 404 Not Found
2425
+
2426
+ {
2427
+ "ErrorCode": 404,
2428
+ "ErrorMessage": "File not found",
2429
+ "InformationMessage": null,
2430
+ "Data": null,
2431
+ "Succeeded": false
2432
+ }
2433
+ ```
2434
+
2435
+ #### File uploads in the data layer
2436
+ 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:
2437
+
2438
+ ```json
2439
+ {
2440
+ "fileId": "1234567891234567890",
2441
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
2442
+ }
2443
+ ```
2444
+
2445
+ #### File uploads when submitted
2446
+ 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`.
2447
+
2448
+ 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:
2449
+
2450
+ ```json
2451
+ {
2452
+ "passportAttachment": {
2453
+ "fileId": "1234567891234567890",
2454
+ "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
2455
+ }
2456
+ }
2457
+ ```
2458
+
2459
+ #### File uploads backward compatibility
2460
+ If these endpoints are not defined in the service JSON, the temporary save/load logic is skipped entirely.
2461
+ Existing services will continue to work without modification.
2462
+
1611
2463
  ### 🛣️ Routes
1612
2464
  The project uses express.js to serve the following routes:
1613
2465
 
@@ -1616,14 +2468,16 @@ The project uses express.js to serve the following routes:
1616
2468
  - **`/: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
2469
  - **`/: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
2470
  - **`/: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.
2471
+ - **`/:siteId/:pageUrl/view-file/:elementName`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the specified file in a new tab.
2472
+ - **`/:siteId/:pageUrl/delete-file/:elementName`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the delete confirmation page and handles the file delete.
1620
2473
 
1621
2474
  #### Authentication routes:
1622
2475
  - **`/signin-oidc`**: CY Login authentication endpoint.
1623
2476
  - **`/login`**: Redirect to CY Login login page.
1624
2477
  - **`/logout`**: CY Login logout endpoint.
1625
2478
 
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.
2479
+ #### API routes:
2480
+ - **`/apis/:siteId/:pageUrl/upload`**: Uploads a file. Used from the client side JS.
1627
2481
 
1628
2482
  ### 👨‍💻 Enviromental variables
1629
2483
  The environment variables are defined in:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.0.0-alpha.12",
3
+ "version": "1.0.0-alpha.13",
4
4
  "description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
5
5
  "author": "DMRID - DSF Team",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "test:package": "mocha --recursive tests/package/**/*.test.mjs",
46
46
  "test:functional": "mocha --timeout 30000 --recursive tests/functional/**/*.test.mjs",
47
47
  "test:watch": "mocha --watch --timeout 60000 tests/**/*.test.mjs",
48
- "coverage": "c8 --reporter=html --reporter=text --reporter=lcov npm test",
48
+ "coverage": "c8 --reporter=html --reporter=text --reporter=lcov --reporter=cobertura npm test",
49
49
  "coverage:report": "c8 report"
50
50
  },
51
51
  "dependencies": {