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

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
@@ -494,7 +494,9 @@ Content-Type: application/json
494
494
  The API is expected to return a JSON response with the following structure (see [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs) for normalization):
495
495
 
496
496
  **On Success:**
497
- ```json
497
+ ```http
498
+ HTTP/1.1 200 OK
499
+
498
500
  {
499
501
  "Succeeded": true,
500
502
  "ErrorCode": 0,
@@ -503,7 +505,9 @@ The API is expected to return a JSON response with the following structure (see
503
505
  ```
504
506
 
505
507
  **On Failure:**
506
- ```json
508
+ ```http
509
+ HTTP/1.1 200 OK
510
+
507
511
  {
508
512
  "Succeeded": false,
509
513
  "ErrorCode": 102,
@@ -637,7 +641,9 @@ Content-Type: application/json
637
641
  The API is expected to return a JSON response with the following structure (see [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs) for normalization):
638
642
 
639
643
  **On Success:**
640
- ```json
644
+ ```http
645
+ HTTP/1.1 200 OK
646
+
641
647
  {
642
648
  "Succeeded": true,
643
649
  "ErrorCode": 0,
@@ -649,7 +655,9 @@ The API is expected to return a JSON response with the following structure (see
649
655
  ```
650
656
 
651
657
  **On Failure:**
652
- ```json
658
+ ```http
659
+ HTTP/1.1 200 OK
660
+
653
661
  {
654
662
  "Succeeded": false,
655
663
  "ErrorCode": 102,
@@ -1419,8 +1427,8 @@ Explanation:
1419
1427
  The **temporary save** feature allows user progress to be stored in an external API and automatically reloaded on the next visit.
1420
1428
  This is useful for long forms or cases where users may leave and return later.
1421
1429
 
1422
- #### 1. Configure the endpoints in your service JSON
1423
- In your service’s `site` object, add both a `submissionGetAPIEndpoint` and `submissionPutAPIEndpoint` entry:
1430
+ #### How to configure temporary save
1431
+ To use this feature, configure the config JSON file. In your service’s `site` object, add both a `submissionGetAPIEndpoint` and `submissionPutAPIEndpoint` entry:
1424
1432
 
1425
1433
  ```json
1426
1434
  "submissionGetAPIEndpoint": {
@@ -1439,7 +1447,6 @@ In your service’s `site` object, add both a `submissionGetAPIEndpoint` and `su
1439
1447
 
1440
1448
  These values should point to environment variables that hold your real endpoint URLs and credentials.
1441
1449
 
1442
- #### 2. Add environment variables
1443
1450
  In your `secrets/.env` file (and staging/production configs), define the variables referenced above:
1444
1451
 
1445
1452
  ```dotenv
@@ -1449,17 +1456,155 @@ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
1449
1456
  TEST_SUBMISSION_API_SERVICE_ID=123
1450
1457
  ```
1451
1458
 
1452
- #### 3. How it works
1459
+ #### How temporary save works
1453
1460
 
1454
- - **On first page load** for a site, `govcyLoadSubmissionData` will:
1461
+ - **On first page load** for a site, using the `submissionGetAPIEndpoint` the system will:
1455
1462
  1. Call the GET endpoint to retrieve any saved submission.
1456
1463
  2. If found, populate the session’s `inputData` so fields are pre-filled.
1457
1464
  3. If not found, call the PUT endpoint to create a new temporary record.
1458
1465
  - **On every form POST**, after successful validation:
1459
- - The `govcyFormsPostHandler` will fire-and-forget a `PUT` request to update the saved submission with the latest form data.
1466
+ - The `submissionPutAPIEndpoint` will fire-and-forget a `PUT` request to update the saved submission with the latest form data.
1460
1467
  - The payload includes all required submission fields with `submission_data` JSON-stringified.
1461
1468
 
1462
- #### 4. Backward compatibility
1469
+ #### `submissionGetAPIEndpoint` `GET` API Request and Response
1470
+ This API is used to retrieve the saved submission data.
1471
+
1472
+ **Request:**
1473
+
1474
+ - **HTTP Method**: GET
1475
+ - **URL**: Resolved from the url property in your config (from the environment variable).
1476
+ - **Headers**:
1477
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
1478
+ - **client-key**: `<clientKey>` (from config/env)
1479
+ - **service-id**: `<serviceId>` (from config/env)
1480
+ - **Accept**: `text/plain`
1481
+ - **Body**: The body contains the and either:
1482
+ - an a `null` which means no data was found for the user
1483
+ - a JSON object with all the form data collected from the user across all pages in previous sessions.
1484
+
1485
+ **Example Request:**
1486
+
1487
+ ```http
1488
+ GET /temp-save-get-endpoint?status=0 HTTP/1.1
1489
+ Host: localhost:3002
1490
+ Authorization: Bearer eyJhbGciOi...
1491
+ client-key: 12345678901234567890123456789000
1492
+ service-id: 123
1493
+ Accept: text/plain
1494
+ Content-Type: application/json
1495
+ ```
1496
+
1497
+ **Response:**
1498
+
1499
+ The API is expected to return a JSON response with the following structure:
1500
+
1501
+ **When temporary submission data are found:**
1502
+
1503
+ ```http
1504
+ HTTP/1.1 200 OK
1505
+
1506
+ {
1507
+ "Succeeded": true,
1508
+ "ErrorCode": 0,
1509
+ "ErrorMessage": null,
1510
+ "Data": {
1511
+ "submissionData": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
1512
+ }
1513
+ }
1514
+ ```
1515
+
1516
+ **When temporary submission data are NOT found:**
1517
+
1518
+ ```http
1519
+ HTTP/1.1 404 Not Found
1520
+
1521
+ {
1522
+ "Succeeded": true,
1523
+ "ErrorCode": 0,
1524
+ "ErrorMessage": null,
1525
+ "Data": null
1526
+ }
1527
+ ```
1528
+
1529
+ **When temporary submission retreival fails:**
1530
+
1531
+ ```http
1532
+ HTTP/1.1 200 OK
1533
+
1534
+ {
1535
+ "Succeeded": false,
1536
+ "ErrorCode": 401,
1537
+ "ErrorMessage": "Not authorized",
1538
+ "Data": null
1539
+ }
1540
+ ```
1541
+
1542
+ #### `submissionPutAPIEndpoint` `PUT` API Request and Response
1543
+ This API is used to temporary save the submission data.
1544
+
1545
+ **Request:**
1546
+
1547
+ - **HTTP Method**: PUT
1548
+ - **URL**: Resolved from the url property in your config (from the environment variable).
1549
+ - **Headers**:
1550
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
1551
+ - **client-key**: `<clientKey>` (from config/env)
1552
+ - **service-id**: `<serviceId>` (from config/env)
1553
+ - **Accept**: `text/plain`
1554
+ - **Body**: The body contains the a JSON object with all the form data collected from the user across all pages.
1555
+
1556
+ **Example Request:**
1557
+
1558
+ ```http
1559
+ PUT /temp-save-endpoint HTTP/1.1
1560
+ Host: localhost:3002
1561
+ Authorization: Bearer eyJhbGciOi...
1562
+ client-key: 12345678901234567890123456789000
1563
+ service-id: 123
1564
+ Accept: text/plain
1565
+ Content-Type: application/json
1566
+
1567
+ {
1568
+ "submission_data" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
1569
+ }
1570
+ ```
1571
+
1572
+ **Response:**
1573
+
1574
+ The API is expected to return a JSON response with the following structure:
1575
+
1576
+ **On success:**
1577
+
1578
+ ```http
1579
+ HTTP/1.1 200 OK
1580
+
1581
+ {
1582
+ "Succeeded": true,
1583
+ "ErrorCode": 0,
1584
+ "ErrorMessage": null,
1585
+ "Data": {
1586
+ "submissionData": "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
1587
+ }
1588
+ }
1589
+ ```
1590
+
1591
+ **On failure:**
1592
+
1593
+ ```http
1594
+ HTTP/1.1 401 Unauthorized
1595
+
1596
+ {
1597
+ "Succeeded": false,
1598
+ "ErrorCode": 401,
1599
+ "ErrorMessage": "Not authorized",
1600
+ "Data": null
1601
+ }
1602
+ ```
1603
+
1604
+ **Notes**:
1605
+ - The response is normalized to always use PascalCase keys (`Succeeded`, `ErrorCode`, etc.), regardless of the backend’s casing.
1606
+
1607
+ #### Temporary save backward compatibility
1463
1608
  If these endpoints are not defined in the service JSON, the temporary save/load logic is skipped entirely.
1464
1609
  Existing services will continue to work without modification.
1465
1610
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.4",
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",
@@ -11,6 +11,7 @@ import { logger } from "./govcyLogger.mjs";
11
11
  * @param {object} headers - Custom headers (optional)
12
12
  * @param {number} retries - Number of retry attempts (default: 3)
13
13
  * @param {boolean} allowSelfSignedCerts - Whether to allow self-signed certificates (default: false)
14
+ * @param {array} allowedHTTPStatusCodes - Array of allowed HTTP status codes (default: [200])
14
15
  * @returns {Promise<object>} - API response
15
16
  */
16
17
  export async function govcyApiRequest(
@@ -21,7 +22,8 @@ export async function govcyApiRequest(
21
22
  user = null,
22
23
  headers = {},
23
24
  retries = 3,
24
- allowSelfSignedCerts = false
25
+ allowSelfSignedCerts = false,
26
+ allowedHTTPStatusCodes = [200]
25
27
  ) {
26
28
  let attempt = 0;
27
29
 
@@ -48,6 +50,8 @@ export async function govcyApiRequest(
48
50
  [method?.toLowerCase() === 'get' ? 'params' : 'data']: inputData,
49
51
  headers: requestHeaders,
50
52
  timeout: 10000, // 10 seconds timeout
53
+ // ✅ Treat only these statuses as "resolved" (no throw)
54
+ validateStatus: (status) => allowedHTTPStatusCodes.includes(status),
51
55
  };
52
56
 
53
57
  // Add httpsAgent if NOT production to allow self-signed certificates
@@ -60,9 +64,13 @@ export async function govcyApiRequest(
60
64
 
61
65
  logger.debug(`📥 Received API response`, { status: response.status, data: response.data });
62
66
 
63
- if (response.status !== 200) {
67
+ // Validate HTTP status
68
+ if (!allowedHTTPStatusCodes.includes(response.status)) {
64
69
  throw new Error(`Unexpected HTTP status: ${response.status}`);
65
70
  }
71
+ // if (response.status !== 200) {
72
+ // throw new Error(`Unexpected HTTP status: ${response.status}`);
73
+ // }
66
74
 
67
75
  // const { Succeeded, ErrorCode, ErrorMessage } = response.data;
68
76
  // Normalize to PascalCase regardless of input case
@@ -77,7 +85,7 @@ export async function govcyApiRequest(
77
85
  ErrorMessage,
78
86
  Data,
79
87
  InformationMessage
80
- } = response.data;
88
+ } = response.data ?? {};
81
89
 
82
90
  const normalized = {
83
91
  Succeeded: Succeeded !== undefined ? Succeeded : succeeded,
@@ -73,7 +73,8 @@ export async function govcyLoadSubmissionDataAPIs(store, service, siteId, next)
73
73
  ...(getCfgDsfGtwApiKey !== '' && { "dsfgtw-api-key": getCfgDsfGtwApiKey }) // Use the DSF API GTW secret from environment variables
74
74
  },
75
75
  3,
76
- allowSelfSignedCerts
76
+ allowSelfSignedCerts,
77
+ [200, 404] // Allowed HTTP status codes
77
78
  );
78
79
 
79
80
  // If not succeeded, handle error
@@ -88,7 +89,9 @@ export async function govcyLoadSubmissionDataAPIs(store, service, siteId, next)
88
89
  dataLayer.storeSiteLoadData(store, siteId, getResponse.Data);
89
90
 
90
91
  try {
91
- const parsed = JSON.parse(getResponse.Data.submissionData || "{}");
92
+ const parsed = JSON.parse(getResponse.Data.submissionData
93
+ || getResponse.Data.submission_data
94
+ || "{}");
92
95
  if (parsed && typeof parsed === "object") {
93
96
  dataLayer.storeSiteInputData(store, siteId, parsed);
94
97
  logger.debug(`💾 Input data restored from saved submission for siteId: ${siteId}`);
@@ -99,11 +102,15 @@ export async function govcyLoadSubmissionDataAPIs(store, service, siteId, next)
99
102
 
100
103
  // if not call the PUT submission API
101
104
  } else {
105
+ const tempPutPayload = {
106
+ // submission_data: JSON.stringify({}),
107
+ submissionData: JSON.stringify({})
108
+ };
102
109
  // If no data, call the PUT submission API to create it
103
110
  const putResponse = await govcyApiRequest(
104
111
  putCfgMethod,
105
112
  putCfgUrl,
106
- putCfgParams,
113
+ tempPutPayload,
107
114
  true, // use access token auth
108
115
  user,
109
116
  {
@@ -27,7 +27,8 @@ export async function tempSaveIfConfigured(store, service, siteId) {
27
27
  const inputData = dataLayer.getSiteInputData(store, siteId) || {};
28
28
  const tempPayload = {
29
29
  // mirror final submission format: send stringified JSON
30
- submission_data: JSON.stringify(inputData)
30
+ // submission_data: JSON.stringify(inputData),
31
+ submissionData: JSON.stringify(inputData)
31
32
  };
32
33
 
33
34
  if (!url || !clientKey) {