@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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
####
|
|
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
|
-
####
|
|
1459
|
+
#### How temporary save works
|
|
1453
1460
|
|
|
1454
|
-
- **On first page load** for a site, `
|
|
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 `
|
|
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
|
-
####
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|