Add Pennyzo API status checker scripts, report viewer, and documentation
This commit is contained in:
302
package-lock.json
generated
302
package-lock.json
generated
@@ -8,6 +8,8 @@
|
|||||||
"name": "pennyzo-web",
|
"name": "pennyzo-web",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.12.2",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"typescript": "^4.9.0"
|
"typescript": "^4.9.0"
|
||||||
@@ -1395,7 +1397,6 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
@@ -1435,6 +1436,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.21",
|
"version": "10.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||||
@@ -1473,6 +1480,17 @@
|
|||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||||
|
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -1550,6 +1568,19 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/camelcase-css": {
|
"node_modules/camelcase-css": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
@@ -1581,6 +1612,22 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@@ -1623,7 +1670,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
@@ -1636,9 +1682,20 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/convert-source-map": {
|
"node_modules/convert-source-map": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
@@ -1699,6 +1756,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/didyoumean": {
|
"node_modules/didyoumean": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
@@ -1713,6 +1779,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@@ -1734,6 +1814,51 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.9",
|
"version": "0.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
|
||||||
@@ -1839,6 +1964,26 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||||
@@ -1869,6 +2014,22 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
@@ -1902,7 +2063,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -1918,6 +2078,43 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -1931,11 +2128,58 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -2110,6 +2354,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@@ -2134,6 +2387,27 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mini-svg-data-uri": {
|
"node_modules/mini-svg-data-uri": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||||
@@ -2509,6 +2783,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@@ -2889,6 +3169,18 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-preserve-symlinks-flag": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.12.2",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"typescript": "^4.9.0"
|
"typescript": "^4.9.0"
|
||||||
@@ -22,6 +24,10 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"api:check": "node scripts/api-status-checker.js",
|
||||||
|
"api:check:extended": "node scripts/api-status-checker-extended.js",
|
||||||
|
"api:check:verbose": "node scripts/api-status-checker-extended.js --verbose",
|
||||||
|
"api:check:real": "node scripts/api-status-checker-extended.js --use-real-credentials"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
reports/.gitignore
vendored
Normal file
5
reports/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Ignore all JSON files in this directory (API status reports)
|
||||||
|
*.json
|
||||||
|
|
||||||
|
# But don't ignore any specifically created example files
|
||||||
|
!example-*.json
|
||||||
31
reports/README.md
Normal file
31
reports/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Reports Directory
|
||||||
|
|
||||||
|
This directory contains API status check reports generated by the API status checker tool.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- **JSON Reports**: Generated when running the extended API status checker
|
||||||
|
- **example-report.json**: Sample report showing the expected format
|
||||||
|
- **report-viewer.html**: HTML tool for visualizing the JSON reports
|
||||||
|
|
||||||
|
## Using the Report Viewer
|
||||||
|
|
||||||
|
To view a report:
|
||||||
|
|
||||||
|
1. Open the `report-viewer.html` file in a web browser
|
||||||
|
2. Click "Choose Report File" to select a JSON report
|
||||||
|
3. Review the report details including endpoint statuses
|
||||||
|
|
||||||
|
## Report Format
|
||||||
|
|
||||||
|
Each report includes:
|
||||||
|
|
||||||
|
- Summary statistics (success rate, endpoint counts)
|
||||||
|
- Detailed endpoint results
|
||||||
|
- Response times
|
||||||
|
- Error details
|
||||||
|
- API response data (when in verbose mode)
|
||||||
|
|
||||||
|
## Retention Policy
|
||||||
|
|
||||||
|
By default, all JSON reports in this directory are ignored by git. If you want to keep certain reports, consider copying them elsewhere or modifying the `.gitignore` file in this directory.
|
||||||
74
reports/example-report.json
Normal file
74
reports/example-report.json
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"title": "Pennyzo API Status Check Report",
|
||||||
|
"timestamp": "2025-09-25T10:15:30.123Z",
|
||||||
|
"duration": "1.15 seconds",
|
||||||
|
"summary": {
|
||||||
|
"totalEndpoints": 3,
|
||||||
|
"successful": 2,
|
||||||
|
"failed": 1,
|
||||||
|
"successRate": "66.7%"
|
||||||
|
},
|
||||||
|
"baseUrl": "https://api.pennyzo.com/api/v1/general",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"endpoint": "/system/health",
|
||||||
|
"method": "GET",
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"statusCode": 200,
|
||||||
|
"responseTime": 342,
|
||||||
|
"timestamp": "2025-09-25T10:15:30.123Z",
|
||||||
|
"error": null,
|
||||||
|
"data": {
|
||||||
|
"status": "operational",
|
||||||
|
"service": "Pennyzo General API",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"environment": "production",
|
||||||
|
"message": "Service is healthy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"endpoint": "/registration/company/identity-rules",
|
||||||
|
"method": "GET",
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"statusCode": 200,
|
||||||
|
"responseTime": 285,
|
||||||
|
"timestamp": "2025-09-25T10:15:30.456Z",
|
||||||
|
"error": null,
|
||||||
|
"data": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"company_type": "PRIVATE_LIMITED",
|
||||||
|
"required_fields": ["cin_number"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_type": "LLP",
|
||||||
|
"required_fields": ["llp_number"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_type": "PROPRIETORSHIP",
|
||||||
|
"required_fields": ["pan_number"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"endpoint": "/auth/login",
|
||||||
|
"method": "POST",
|
||||||
|
"status": "FAILED",
|
||||||
|
"statusCode": 401,
|
||||||
|
"responseTime": 520,
|
||||||
|
"timestamp": "2025-09-25T10:15:30.789Z",
|
||||||
|
"error": {
|
||||||
|
"message": "Request failed with status code 401",
|
||||||
|
"details": {
|
||||||
|
"error": {
|
||||||
|
"type": "unauthorized",
|
||||||
|
"code": 401,
|
||||||
|
"message": "Invalid credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
378
reports/report-viewer.html
Normal file
378
reports/report-viewer.html
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pennyzo API Status Report Viewer</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--deep-navy: #05174d;
|
||||||
|
--brass: #d4af37;
|
||||||
|
--off-white: #f5f5f5;
|
||||||
|
--light-gray: #e0e0e0;
|
||||||
|
--success: #28a745;
|
||||||
|
--danger: #dc3545;
|
||||||
|
--warning: #ffc107;
|
||||||
|
--info: #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--off-white);
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: var(--deep-navy);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 4px solid var(--brass);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-selector {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: var(--deep-navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card .value {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-rate {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: var(--deep-navy);
|
||||||
|
color: white;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background-color: var(--success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-failed {
|
||||||
|
background-color: var(--danger);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-GET {
|
||||||
|
color: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-POST {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-PUT {
|
||||||
|
color: var(--deep-navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-DELETE {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-row {
|
||||||
|
display: none;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-cell {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-container {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandable::after {
|
||||||
|
content: '▼';
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-btn {
|
||||||
|
background-color: var(--deep-navy);
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-report {
|
||||||
|
padding: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Pennyzo API Status Report Viewer</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="report-selector">
|
||||||
|
<h2>Select Report</h2>
|
||||||
|
<input type="file" id="file-input" accept=".json">
|
||||||
|
<button class="file-upload-btn" onclick="document.getElementById('file-input').click()">
|
||||||
|
Choose Report File
|
||||||
|
</button>
|
||||||
|
<span class="file-name" id="file-name"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="report-content">
|
||||||
|
<div class="no-report">
|
||||||
|
<h2>No Report Loaded</h2>
|
||||||
|
<p>Please select a report JSON file to view its contents.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Pennyzo API Status Report Viewer | © 2025 Pennyzo</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('file-input').addEventListener('change', function(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
document.getElementById('file-name').textContent = file.name;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
try {
|
||||||
|
const report = JSON.parse(e.target.result);
|
||||||
|
renderReport(report);
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error parsing JSON file: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderReport(report) {
|
||||||
|
const reportContent = document.getElementById('report-content');
|
||||||
|
|
||||||
|
// Create HTML for report
|
||||||
|
let html = `
|
||||||
|
<h2>${report.title}</h2>
|
||||||
|
<p><strong>Generated:</strong> ${new Date(report.timestamp).toLocaleString()}</p>
|
||||||
|
<p><strong>Duration:</strong> ${report.duration}</p>
|
||||||
|
<p><strong>Base URL:</strong> ${report.baseUrl}</p>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<div class="summary-card">
|
||||||
|
<h3>Total Endpoints</h3>
|
||||||
|
<div class="value">${report.summary.totalEndpoints}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<h3>Successful</h3>
|
||||||
|
<div class="value" style="color: var(--success)">${report.summary.successful}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<h3>Failed</h3>
|
||||||
|
<div class="value" style="color: var(--danger)">${report.summary.failed}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<h3>Success Rate</h3>
|
||||||
|
<div class="value success-rate">${report.summary.successRate}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Endpoint Results</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Endpoint</th>
|
||||||
|
<th>Method</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Status Code</th>
|
||||||
|
<th>Response Time</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add rows for each result
|
||||||
|
report.results.forEach((result, index) => {
|
||||||
|
html += `
|
||||||
|
<tr class="expandable" onclick="toggleDetails(${index})">
|
||||||
|
<td>${result.endpoint}</td>
|
||||||
|
<td><span class="method method-${result.method}">${result.method}</span></td>
|
||||||
|
<td><span class="status status-${result.status.toLowerCase()}">${result.status}</span></td>
|
||||||
|
<td>${result.statusCode}</td>
|
||||||
|
<td>${result.responseTime}ms</td>
|
||||||
|
<td>${new Date(result.timestamp).toLocaleTimeString()}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="details-${index}" class="details-row">
|
||||||
|
<td colspan="6" class="details-cell">
|
||||||
|
<div class="details-container">
|
||||||
|
${renderDetails(result)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
reportContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDetails(result) {
|
||||||
|
let details = '';
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
details += `<strong>Error Message:</strong> ${result.error.message}\n\n`;
|
||||||
|
if (result.error.details) {
|
||||||
|
details += `<strong>Error Details:</strong>\n${JSON.stringify(result.error.details, null, 2)}\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.data) {
|
||||||
|
details += `<strong>Response Data:</strong>\n${JSON.stringify(result.data, null, 2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return details || 'No additional details available';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDetails(index) {
|
||||||
|
const detailsRow = document.getElementById(`details-${index}`);
|
||||||
|
if (detailsRow.style.display === 'table-row') {
|
||||||
|
detailsRow.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
detailsRow.style.display = 'table-row';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load example report on page load
|
||||||
|
fetch('example-report.json')
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Example report not found');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(report => {
|
||||||
|
document.getElementById('file-name').textContent = 'example-report.json (pre-loaded)';
|
||||||
|
renderReport(report);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('No example report pre-loaded:', error);
|
||||||
|
// No need to show an error, we'll just show the default "No Report Loaded" message
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
253
scripts/README.md
Normal file
253
scripts/README.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# Pennyzo API Status Checker
|
||||||
|
|
||||||
|
Node.js scripts for checking the status of all primary endpoints of the Pennyzo General API.
|
||||||
|
|
||||||
|
This package includes two versions of the script:
|
||||||
|
|
||||||
|
- `api-status-checker.js` - Basic version with console output
|
||||||
|
- `api-status-checker-extended.js` - Extended version with report generation and additional features
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
These scripts test both public and authenticated endpoints of the Pennyzo General API and log the results to the console. The scripts perform the following checks:
|
||||||
|
|
||||||
|
### Public Endpoints:
|
||||||
|
|
||||||
|
- GET `/system/health`
|
||||||
|
- GET `/registration/company/identity-rules`
|
||||||
|
|
||||||
|
### Authentication Flow:
|
||||||
|
|
||||||
|
- POST `/auth/login` (using placeholder credentials)
|
||||||
|
|
||||||
|
### Authenticated Endpoints (only if login is successful):
|
||||||
|
|
||||||
|
- GET `/auth/me`
|
||||||
|
- GET `/users/profile`
|
||||||
|
- GET `/users/stats`
|
||||||
|
- GET `/users/membership-info`
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js (version 14 or higher recommended)
|
||||||
|
- npm packages:
|
||||||
|
- axios (for HTTP requests)
|
||||||
|
- chalk (for colorful console output)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Both required packages are already installed in the project.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Using npm Scripts (Recommended)
|
||||||
|
|
||||||
|
The following npm scripts are available for checking API status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run basic API check
|
||||||
|
npm run api:check
|
||||||
|
|
||||||
|
# Run extended API check with report generation
|
||||||
|
npm run api:check:extended
|
||||||
|
|
||||||
|
# Run extended API check with verbose output
|
||||||
|
npm run api:check:verbose
|
||||||
|
|
||||||
|
# Run extended API check with real credentials
|
||||||
|
npm run api:check:real
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct Node Execution
|
||||||
|
|
||||||
|
To run the scripts directly with Node:
|
||||||
|
|
||||||
|
#### Basic Script
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node scripts/api-status-checker.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Extended Script
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node scripts/api-status-checker-extended.js [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
- `--verbose`: Show detailed response data
|
||||||
|
- `--no-report`: Don't save a report file
|
||||||
|
- `--use-real-credentials`: Use credentials from environment variables
|
||||||
|
|
||||||
|
### Helper PowerShell Script
|
||||||
|
|
||||||
|
For additional flexibility, a PowerShell helper script is provided:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\run-api-check.ps1 [-Verbose] [-NoReport] [-UseRealCredentials] [-Extended]
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Run basic script
|
||||||
|
.\scripts\run-api-check.ps1
|
||||||
|
|
||||||
|
# Run extended script with verbose output
|
||||||
|
.\scripts\run-api-check.ps1 -Extended -Verbose
|
||||||
|
|
||||||
|
# Run extended script with real credentials
|
||||||
|
.\scripts\run-api-check.ps1 -Extended -UseRealCredentials
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Basic Configuration
|
||||||
|
|
||||||
|
You can modify the test credentials in the script to use valid credentials for testing authenticated endpoints:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Test credentials (replace with actual test credentials if available)
|
||||||
|
const TEST_CREDENTIALS = {
|
||||||
|
email: "your-test-email@example.com",
|
||||||
|
password: "your-test-password",
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
For the extended script, you can set environment variables for secure credential management:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:PENNYZO_TEST_EMAIL = "your-test-email@example.com"
|
||||||
|
$env:PENNYZO_TEST_PASSWORD = "your-test-password"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run with the `--use-real-credentials` flag:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node scripts/api-status-checker-extended.js --use-real-credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
The script provides detailed output for each API call:
|
||||||
|
|
||||||
|
- Endpoint being tested
|
||||||
|
- HTTP method
|
||||||
|
- Status (SUCCESS or FAILED)
|
||||||
|
- HTTP status code
|
||||||
|
- Error details (if applicable)
|
||||||
|
|
||||||
|
For the system health endpoint, additional information is displayed:
|
||||||
|
|
||||||
|
- API Status
|
||||||
|
- Environment
|
||||||
|
- Version
|
||||||
|
- Message
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Basic Script
|
||||||
|
|
||||||
|
- Tests both public and authenticated endpoints
|
||||||
|
- Colorful console output with chalk
|
||||||
|
- Clear status indicators
|
||||||
|
|
||||||
|
### Extended Script (Additional Features)
|
||||||
|
|
||||||
|
- Saves detailed reports to JSON files (in a `reports` folder)
|
||||||
|
- Provides response time measurements
|
||||||
|
- Supports command line arguments
|
||||||
|
- Environment variable support for credentials
|
||||||
|
- Verbose mode for detailed output
|
||||||
|
- Summary statistics
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
### Basic Script Output
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Pennyzo General API Status Checker ===
|
||||||
|
|
||||||
|
--- Testing Public Endpoints ---
|
||||||
|
|
||||||
|
GET /system/health: SUCCESS (200)
|
||||||
|
API Status: operational
|
||||||
|
Environment: production
|
||||||
|
Version: 1.0.0
|
||||||
|
Message: Service is healthy
|
||||||
|
|
||||||
|
GET /registration/company/identity-rules: SUCCESS (200)
|
||||||
|
|
||||||
|
--- Testing Authentication Flow ---
|
||||||
|
|
||||||
|
POST /auth/login: FAILED (401)
|
||||||
|
Error: Request failed with status code 401
|
||||||
|
Details: {
|
||||||
|
"error": {
|
||||||
|
"type": "unauthorized",
|
||||||
|
"code": 401,
|
||||||
|
"message": "Invalid credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Authentication failed. Cannot test authenticated endpoints.
|
||||||
|
Note: This is expected if using placeholder credentials.
|
||||||
|
To test authenticated endpoints, update the TEST_CREDENTIALS with valid credentials.
|
||||||
|
|
||||||
|
=== API Status Check Complete ===
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extended Script Output
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Pennyzo General API Status Checker ===
|
||||||
|
|
||||||
|
Timestamp: 2025-09-25T10:15:30.123Z
|
||||||
|
Base URL: https://api.pennyzo.com/api/v1/general
|
||||||
|
Save Report: Yes
|
||||||
|
Verbose Mode: Yes
|
||||||
|
Using Real Credentials: No
|
||||||
|
|
||||||
|
--- Testing Public Endpoints ---
|
||||||
|
|
||||||
|
GET /system/health: SUCCESS (200) [342ms]
|
||||||
|
API Status: operational
|
||||||
|
Environment: production
|
||||||
|
Version: 1.0.0
|
||||||
|
Message: Service is healthy
|
||||||
|
|
||||||
|
GET /registration/company/identity-rules: SUCCESS (200) [285ms]
|
||||||
|
|
||||||
|
--- Testing Authentication Flow ---
|
||||||
|
|
||||||
|
POST /auth/login: FAILED (401) [520ms]
|
||||||
|
Error: Request failed with status code 401
|
||||||
|
|
||||||
|
Authentication failed. Cannot test authenticated endpoints.
|
||||||
|
Note: This is expected if using placeholder credentials.
|
||||||
|
To test authenticated endpoints, use the --use-real-credentials flag
|
||||||
|
and set PENNYZO_TEST_EMAIL and PENNYZO_TEST_PASSWORD environment variables.
|
||||||
|
|
||||||
|
=== API Status Check Summary ===
|
||||||
|
|
||||||
|
Total endpoints checked: 3
|
||||||
|
Successful: 2
|
||||||
|
Failed: 1
|
||||||
|
Success rate: 66.7%
|
||||||
|
Total time: 1.15 seconds
|
||||||
|
|
||||||
|
Report saved to: c:\Users\naman\OneDrive\Desktop\Pennyzo Web\reports\api-status-2025-09-25T10-15-30.123Z.json
|
||||||
|
|
||||||
|
=== API Status Check Complete ===
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The scripts use placeholder credentials by default, which will likely fail authentication. This is expected behavior.
|
||||||
|
- To properly test authenticated endpoints, use real credentials via environment variables or direct script modification.
|
||||||
|
- The extended script saves reports to a `reports` directory for historical tracking of API status.
|
||||||
|
- All API calls and their results are logged to the console and (optionally) to report files.
|
||||||
|
- Response times are logged to help identify slow endpoints.
|
||||||
|
- A HTML report viewer is included at `reports/report-viewer.html` for visualizing the JSON reports.
|
||||||
101
scripts/SUMMARY.md
Normal file
101
scripts/SUMMARY.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Pennyzo API Status Checker
|
||||||
|
|
||||||
|
This project provides comprehensive tools for checking the status of the Pennyzo General API endpoints. It includes both basic and extended versions of the API status checker, along with supporting tools and documentation.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/
|
||||||
|
├── api-status-checker.js # Basic API status checker
|
||||||
|
├── api-status-checker-extended.js # Extended API status checker with additional features
|
||||||
|
├── run-api-check.ps1 # PowerShell helper script
|
||||||
|
└── README.md # Documentation for the scripts
|
||||||
|
|
||||||
|
reports/
|
||||||
|
├── example-report.json # Sample report file
|
||||||
|
├── report-viewer.html # HTML tool for visualizing reports
|
||||||
|
├── .gitignore # Git configuration for reports
|
||||||
|
└── README.md # Documentation for the reports directory
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
1. **Basic API Status Checker**
|
||||||
|
|
||||||
|
- Tests both public and authenticated endpoints
|
||||||
|
- Colorful console output
|
||||||
|
- Automatic token management
|
||||||
|
|
||||||
|
2. **Extended API Status Checker**
|
||||||
|
|
||||||
|
- All features of the basic checker
|
||||||
|
- Saves detailed reports to JSON files
|
||||||
|
- Provides response time measurements
|
||||||
|
- Support for command line arguments
|
||||||
|
- Environment variable support for credentials
|
||||||
|
|
||||||
|
3. **Report Viewer**
|
||||||
|
|
||||||
|
- HTML-based visualization tool
|
||||||
|
- Interactive endpoint details
|
||||||
|
- Summary statistics
|
||||||
|
- Color-coded status indicators
|
||||||
|
|
||||||
|
4. **Helper Tools**
|
||||||
|
- PowerShell script for flexible execution
|
||||||
|
- npm scripts for convenient access
|
||||||
|
- Example report for reference
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Using npm Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run basic API check
|
||||||
|
npm run api:check
|
||||||
|
|
||||||
|
# Run extended API check with report generation
|
||||||
|
npm run api:check:extended
|
||||||
|
|
||||||
|
# Run extended API check with verbose output
|
||||||
|
npm run api:check:verbose
|
||||||
|
|
||||||
|
# Run extended API check with real credentials
|
||||||
|
npm run api:check:real
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Environment Variables
|
||||||
|
|
||||||
|
For secure credential management with real credentials:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:PENNYZO_TEST_EMAIL = "your-test-email@example.com"
|
||||||
|
$env:PENNYZO_TEST_PASSWORD = "your-test-password"
|
||||||
|
npm run api:check:real
|
||||||
|
```
|
||||||
|
|
||||||
|
### Viewing Reports
|
||||||
|
|
||||||
|
After running the extended script, reports are saved to the `reports` directory. To view a report:
|
||||||
|
|
||||||
|
1. Open `reports/report-viewer.html` in a web browser
|
||||||
|
2. Click "Choose Report File" to select a JSON report
|
||||||
|
3. Review the report details
|
||||||
|
|
||||||
|
## Summary of API Endpoints Tested
|
||||||
|
|
||||||
|
### Public Endpoints
|
||||||
|
|
||||||
|
- GET `/system/health`
|
||||||
|
- GET `/registration/company/identity-rules`
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
|
||||||
|
- POST `/auth/login`
|
||||||
|
|
||||||
|
### Authenticated Endpoints
|
||||||
|
|
||||||
|
- GET `/auth/me`
|
||||||
|
- GET `/users/profile`
|
||||||
|
- GET `/users/stats`
|
||||||
|
- GET `/users/membership-info`
|
||||||
234
scripts/api-status-checker-extended.js
Normal file
234
scripts/api-status-checker-extended.js
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* Pennyzo General API Status Checker (Extended Version)
|
||||||
|
*
|
||||||
|
* This script checks the status of all primary endpoints of the Pennyzo General API,
|
||||||
|
* logs the results to the console, and optionally saves a detailed report to a file.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Tests both public and authenticated endpoints
|
||||||
|
* - Provides detailed error information
|
||||||
|
* - Generates a report with timestamp
|
||||||
|
* - Saves results to a file
|
||||||
|
* - Supports command line arguments for customization
|
||||||
|
*/
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const saveReport = !args.includes('--no-report');
|
||||||
|
const verbose = args.includes('--verbose');
|
||||||
|
const useRealCredentials = args.includes('--use-real-credentials');
|
||||||
|
|
||||||
|
// Define the base URL
|
||||||
|
const BASE_URL = 'https://api.pennyzo.com/api/v1/general';
|
||||||
|
|
||||||
|
// Test credentials
|
||||||
|
const TEST_CREDENTIALS = {
|
||||||
|
email: useRealCredentials ? process.env.PENNYZO_TEST_EMAIL || 'test@example.com' : 'test@example.com',
|
||||||
|
password: useRealCredentials ? process.env.PENNYZO_TEST_PASSWORD || 'Password123!' : 'Password123!'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store the access token for authenticated requests
|
||||||
|
let accessToken = null;
|
||||||
|
|
||||||
|
// Store all results for reporting
|
||||||
|
const results = [];
|
||||||
|
const startTime = new Date();
|
||||||
|
|
||||||
|
// Helper function to log API call results
|
||||||
|
function logResult(endpoint, method, status, statusCode, responseTime, data = null, error = null) {
|
||||||
|
const statusText = status === 'SUCCESS' ? chalk.green(status) : chalk.red(status);
|
||||||
|
const statusCodeText = statusCode ? `(${statusCode})` : '';
|
||||||
|
const responseTimeText = responseTime ? `[${responseTime}ms]` : '';
|
||||||
|
|
||||||
|
console.log(`${chalk.yellow(method)} ${chalk.blue(endpoint)}: ${statusText} ${statusCodeText} ${responseTimeText}`);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log(` Error: ${chalk.red(error.message)}`);
|
||||||
|
if (error.response?.data && verbose) {
|
||||||
|
console.log(` Details: ${JSON.stringify(error.response.data, null, 2)}`);
|
||||||
|
}
|
||||||
|
} else if (data && verbose) {
|
||||||
|
const dataPreview = JSON.stringify(data).substring(0, 100) + (JSON.stringify(data).length > 100 ? '...' : '');
|
||||||
|
console.log(` Data: ${dataPreview}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store result for reporting
|
||||||
|
results.push({
|
||||||
|
endpoint,
|
||||||
|
method,
|
||||||
|
status,
|
||||||
|
statusCode: statusCode || 'N/A',
|
||||||
|
responseTime,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
error: error ? {
|
||||||
|
message: error.message,
|
||||||
|
details: error.response?.data || null
|
||||||
|
} : null,
|
||||||
|
data: data && verbose ? data : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to make API calls and log results
|
||||||
|
async function callApi(endpoint, method = 'GET', data = null, authenticated = false) {
|
||||||
|
const url = `${BASE_URL}${endpoint}`;
|
||||||
|
const config = {};
|
||||||
|
|
||||||
|
if (authenticated && accessToken) {
|
||||||
|
config.headers = {
|
||||||
|
'Authorization': `Bearer ${accessToken}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for form-urlencoded content type (login endpoint)
|
||||||
|
if (endpoint === '/auth/login') {
|
||||||
|
config.headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
};
|
||||||
|
// Convert data to URLSearchParams for login endpoint
|
||||||
|
if (data) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('username', data.email); // API uses username field for email
|
||||||
|
params.append('password', data.password);
|
||||||
|
data = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
switch (method) {
|
||||||
|
case 'GET':
|
||||||
|
response = await axios.get(url, config);
|
||||||
|
break;
|
||||||
|
case 'POST':
|
||||||
|
response = await axios.post(url, data, config);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported method: ${method}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
logResult(endpoint, method, 'SUCCESS', response.status, responseTime, response.data);
|
||||||
|
return { success: true, data: response.data, responseTime };
|
||||||
|
} catch (error) {
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
const statusCode = error.response?.status || 'N/A';
|
||||||
|
logResult(endpoint, method, 'FAILED', statusCode, responseTime, null, error);
|
||||||
|
return { success: false, error, responseTime };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to generate and save a report
|
||||||
|
function saveReportToFile() {
|
||||||
|
const endTime = new Date();
|
||||||
|
const duration = (endTime - startTime) / 1000; // in seconds
|
||||||
|
|
||||||
|
// Count successes and failures
|
||||||
|
const successCount = results.filter(r => r.status === 'SUCCESS').length;
|
||||||
|
const failureCount = results.filter(r => r.status === 'FAILED').length;
|
||||||
|
|
||||||
|
const report = {
|
||||||
|
title: 'Pennyzo API Status Check Report',
|
||||||
|
timestamp: endTime.toISOString(),
|
||||||
|
duration: `${duration.toFixed(2)} seconds`,
|
||||||
|
summary: {
|
||||||
|
totalEndpoints: results.length,
|
||||||
|
successful: successCount,
|
||||||
|
failed: failureCount,
|
||||||
|
successRate: `${((successCount / results.length) * 100).toFixed(1)}%`
|
||||||
|
},
|
||||||
|
baseUrl: BASE_URL,
|
||||||
|
results: results
|
||||||
|
};
|
||||||
|
|
||||||
|
const reportDir = path.join(__dirname, '../reports');
|
||||||
|
if (!fs.existsSync(reportDir)) {
|
||||||
|
fs.mkdirSync(reportDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportPath = path.join(reportDir, `api-status-${endTime.toISOString().replace(/:/g, '-')}.json`);
|
||||||
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||||
|
|
||||||
|
console.log(chalk.bold(`\nReport saved to: ${reportPath}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function to run the API checks
|
||||||
|
async function checkApiStatus() {
|
||||||
|
console.log(chalk.bold('\n=== Pennyzo General API Status Checker ===\n'));
|
||||||
|
console.log(`Timestamp: ${startTime.toISOString()}`);
|
||||||
|
console.log(`Base URL: ${BASE_URL}`);
|
||||||
|
console.log(`Save Report: ${saveReport ? 'Yes' : 'No'}`);
|
||||||
|
console.log(`Verbose Mode: ${verbose ? 'Yes' : 'No'}`);
|
||||||
|
console.log(`Using Real Credentials: ${useRealCredentials ? 'Yes' : 'No'}\n`);
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n--- Testing Public Endpoints ---\n'));
|
||||||
|
|
||||||
|
// Test system health endpoint
|
||||||
|
const healthResult = await callApi('/system/health');
|
||||||
|
if (healthResult.success) {
|
||||||
|
console.log(` API Status: ${healthResult.data.status}`);
|
||||||
|
console.log(` Environment: ${healthResult.data.environment}`);
|
||||||
|
console.log(` Version: ${healthResult.data.version}`);
|
||||||
|
console.log(` Message: ${healthResult.data.message}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test registration company identity rules endpoint
|
||||||
|
await callApi('/registration/company/identity-rules');
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n--- Testing Authentication Flow ---\n'));
|
||||||
|
|
||||||
|
// Try to authenticate with test credentials
|
||||||
|
const loginResult = await callApi('/auth/login', 'POST', TEST_CREDENTIALS);
|
||||||
|
|
||||||
|
if (loginResult.success) {
|
||||||
|
accessToken = loginResult.data.access_token;
|
||||||
|
console.log(chalk.green(' Authentication successful. Access token received.'));
|
||||||
|
console.log(` Token type: ${loginResult.data.token_type}`);
|
||||||
|
if (loginResult.data.expires_in) {
|
||||||
|
console.log(` Token expires in: ${loginResult.data.expires_in} seconds\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n--- Testing Authenticated Endpoints ---\n'));
|
||||||
|
|
||||||
|
// Test authenticated endpoints
|
||||||
|
await callApi('/auth/me', 'GET', null, true);
|
||||||
|
await callApi('/users/profile', 'GET', null, true);
|
||||||
|
await callApi('/users/stats', 'GET', null, true);
|
||||||
|
await callApi('/users/membership-info', 'GET', null, true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log(chalk.red(' Authentication failed. Cannot test authenticated endpoints.'));
|
||||||
|
console.log(' Note: This is expected if using placeholder credentials.');
|
||||||
|
console.log(' To test authenticated endpoints, use the --use-real-credentials flag');
|
||||||
|
console.log(' and set PENNYZO_TEST_EMAIL and PENNYZO_TEST_PASSWORD environment variables.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
const successCount = results.filter(r => r.status === 'SUCCESS').length;
|
||||||
|
const failureCount = results.filter(r => r.status === 'FAILED').length;
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n=== API Status Check Summary ===\n'));
|
||||||
|
console.log(`Total endpoints checked: ${results.length}`);
|
||||||
|
console.log(`Successful: ${chalk.green(successCount)}`);
|
||||||
|
console.log(`Failed: ${chalk.red(failureCount)}`);
|
||||||
|
console.log(`Success rate: ${((successCount / results.length) * 100).toFixed(1)}%`);
|
||||||
|
console.log(`Total time: ${((new Date() - startTime) / 1000).toFixed(2)} seconds\n`);
|
||||||
|
|
||||||
|
// Save report if enabled
|
||||||
|
if (saveReport) {
|
||||||
|
saveReportToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.bold('=== API Status Check Complete ===\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the API checker
|
||||||
|
checkApiStatus().catch(error => {
|
||||||
|
console.error(chalk.red('\nUnexpected error during API status check:'));
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
137
scripts/api-status-checker.js
Normal file
137
scripts/api-status-checker.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Pennyzo General API Status Checker
|
||||||
|
*
|
||||||
|
* This script checks the status of all primary endpoints of the Pennyzo General API
|
||||||
|
* and logs the results to the console.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
const chalk = require('chalk'); // For colorful console output
|
||||||
|
|
||||||
|
// Define the base URL
|
||||||
|
const BASE_URL = 'https://api.pennyzo.com/api/v1/general';
|
||||||
|
|
||||||
|
// Test credentials (replace with actual test credentials if available)
|
||||||
|
const TEST_CREDENTIALS = {
|
||||||
|
email: 'test@example.com',
|
||||||
|
password: 'Password123!'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store the access token for authenticated requests
|
||||||
|
let accessToken = null;
|
||||||
|
|
||||||
|
// Helper function to log API call results
|
||||||
|
function logResult(endpoint, method, status, statusCode, error = null) {
|
||||||
|
const statusText = status === 'SUCCESS' ? chalk.green(status) : chalk.red(status);
|
||||||
|
const statusCodeText = statusCode ? `(${statusCode})` : '';
|
||||||
|
|
||||||
|
console.log(`${chalk.yellow(method)} ${chalk.blue(endpoint)}: ${statusText} ${statusCodeText}`);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log(` Error: ${chalk.red(error.message)}`);
|
||||||
|
if (error.response?.data) {
|
||||||
|
console.log(` Details: ${JSON.stringify(error.response.data, null, 2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to make API calls and log results
|
||||||
|
async function callApi(endpoint, method = 'GET', data = null, authenticated = false) {
|
||||||
|
const url = `${BASE_URL}${endpoint}`;
|
||||||
|
const config = {};
|
||||||
|
|
||||||
|
if (authenticated && accessToken) {
|
||||||
|
config.headers = {
|
||||||
|
'Authorization': `Bearer ${accessToken}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for form-urlencoded content type (login endpoint)
|
||||||
|
if (endpoint === '/auth/login') {
|
||||||
|
config.headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
};
|
||||||
|
// Convert data to URLSearchParams for login endpoint
|
||||||
|
if (data) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('username', data.email); // API uses username field for email
|
||||||
|
params.append('password', data.password);
|
||||||
|
data = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
switch (method) {
|
||||||
|
case 'GET':
|
||||||
|
response = await axios.get(url, config);
|
||||||
|
break;
|
||||||
|
case 'POST':
|
||||||
|
response = await axios.post(url, data, config);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported method: ${method}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logResult(endpoint, method, 'SUCCESS', response.status);
|
||||||
|
return { success: true, data: response.data };
|
||||||
|
} catch (error) {
|
||||||
|
const statusCode = error.response?.status || 'N/A';
|
||||||
|
logResult(endpoint, method, 'FAILED', statusCode, error);
|
||||||
|
return { success: false, error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function to run the API checks
|
||||||
|
async function checkApiStatus() {
|
||||||
|
console.log(chalk.bold('\n=== Pennyzo General API Status Checker ===\n'));
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n--- Testing Public Endpoints ---\n'));
|
||||||
|
|
||||||
|
// Test system health endpoint
|
||||||
|
const healthResult = await callApi('/system/health');
|
||||||
|
if (healthResult.success) {
|
||||||
|
console.log(` API Status: ${healthResult.data.status}`);
|
||||||
|
console.log(` Environment: ${healthResult.data.environment}`);
|
||||||
|
console.log(` Version: ${healthResult.data.version}`);
|
||||||
|
console.log(` Message: ${healthResult.data.message}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test registration company identity rules endpoint
|
||||||
|
await callApi('/registration/company/identity-rules');
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n--- Testing Authentication Flow ---\n'));
|
||||||
|
|
||||||
|
// Try to authenticate with test credentials
|
||||||
|
const loginResult = await callApi('/auth/login', 'POST', TEST_CREDENTIALS);
|
||||||
|
|
||||||
|
if (loginResult.success) {
|
||||||
|
accessToken = loginResult.data.access_token;
|
||||||
|
console.log(chalk.green(' Authentication successful. Access token received.'));
|
||||||
|
console.log(` Token type: ${loginResult.data.token_type}`);
|
||||||
|
if (loginResult.data.expires_in) {
|
||||||
|
console.log(` Token expires in: ${loginResult.data.expires_in} seconds\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n--- Testing Authenticated Endpoints ---\n'));
|
||||||
|
|
||||||
|
// Test authenticated endpoints
|
||||||
|
await callApi('/auth/me', 'GET', null, true);
|
||||||
|
await callApi('/users/profile', 'GET', null, true);
|
||||||
|
await callApi('/users/stats', 'GET', null, true);
|
||||||
|
await callApi('/users/membership-info', 'GET', null, true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log(chalk.red(' Authentication failed. Cannot test authenticated endpoints.'));
|
||||||
|
console.log(' Note: This is expected if using placeholder credentials.');
|
||||||
|
console.log(' To test authenticated endpoints, update the TEST_CREDENTIALS with valid credentials.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.bold('\n=== API Status Check Complete ===\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the API checker
|
||||||
|
checkApiStatus().catch(error => {
|
||||||
|
console.error(chalk.red('\nUnexpected error during API status check:'));
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
36
scripts/run-api-check.ps1
Normal file
36
scripts/run-api-check.ps1
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# PowerShell script to run the Pennyzo API Status Checker
|
||||||
|
|
||||||
|
param (
|
||||||
|
[switch]$Verbose,
|
||||||
|
[switch]$NoReport,
|
||||||
|
[switch]$UseRealCredentials,
|
||||||
|
[switch]$Extended
|
||||||
|
)
|
||||||
|
|
||||||
|
$ScriptArgs = @()
|
||||||
|
|
||||||
|
if ($Verbose) {
|
||||||
|
$ScriptArgs += "--verbose"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($NoReport) {
|
||||||
|
$ScriptArgs += "--no-report"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UseRealCredentials) {
|
||||||
|
$ScriptArgs += "--use-real-credentials"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine which script to run
|
||||||
|
$ScriptPath = if ($Extended) {
|
||||||
|
"scripts\api-status-checker-extended.js"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
"scripts\api-status-checker.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display command that will be executed
|
||||||
|
Write-Host "Running: node $ScriptPath $ScriptArgs"
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
node $ScriptPath $ScriptArgs
|
||||||
102
src/examples/pennyzo-api-usage-examples.ts
Normal file
102
src/examples/pennyzo-api-usage-examples.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { pennyzoApi } from '../services';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains examples of how to use the PennyzoApiService
|
||||||
|
* These are meant to be reference examples and not actual code to run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Example 1: System health check (public endpoint)
|
||||||
|
async function checkSystemHealth() {
|
||||||
|
try {
|
||||||
|
const health = await pennyzoApi.checkHealth();
|
||||||
|
console.log('System status:', health.status);
|
||||||
|
console.log('Service:', health.service);
|
||||||
|
console.log('Version:', health.version);
|
||||||
|
return health;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking health:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 2: User registration (public endpoint)
|
||||||
|
async function registerNewUser(userData: any) {
|
||||||
|
try {
|
||||||
|
const registeredUser = await pennyzoApi.registerUser({
|
||||||
|
first_name: userData.firstName,
|
||||||
|
last_name: userData.lastName,
|
||||||
|
email: userData.email,
|
||||||
|
company_name: userData.companyName,
|
||||||
|
company_type: userData.companyType,
|
||||||
|
gst_registered: userData.isGstRegistered,
|
||||||
|
gst_number: userData.gstNumber,
|
||||||
|
industry: userData.industry,
|
||||||
|
// Other optional fields
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('User registered successfully:', registeredUser);
|
||||||
|
return registeredUser;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Registration failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 3: Login and authentication flow
|
||||||
|
async function loginUser(email: string, password: string) {
|
||||||
|
try {
|
||||||
|
const loginResult = await pennyzoApi.login(email, password);
|
||||||
|
console.log('Login successful, token type:', loginResult.token_type);
|
||||||
|
console.log('Token expiration:', loginResult.expires_in);
|
||||||
|
|
||||||
|
// Token is automatically stored and will be used in subsequent API calls
|
||||||
|
|
||||||
|
// Get the current user's details (authenticated call)
|
||||||
|
const currentUser = await pennyzoApi.getCurrentUser();
|
||||||
|
console.log('Current user:', currentUser);
|
||||||
|
|
||||||
|
return { loginResult, currentUser };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 4: Update profile (authenticated call)
|
||||||
|
async function updateProfile(profileData: any) {
|
||||||
|
try {
|
||||||
|
const updatedProfile = await pennyzoApi.updateUserProfile({
|
||||||
|
first_name: profileData.firstName,
|
||||||
|
last_name: profileData.lastName,
|
||||||
|
mobile: profileData.mobile,
|
||||||
|
industry: profileData.industry
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Profile updated successfully:', updatedProfile);
|
||||||
|
return updatedProfile;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Profile update failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 5: Logout
|
||||||
|
function logout() {
|
||||||
|
pennyzoApi.logout();
|
||||||
|
console.log('User logged out successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 6: Error handling with the service
|
||||||
|
async function demonstrateErrorHandling() {
|
||||||
|
try {
|
||||||
|
// Trying to access a protected endpoint without authentication
|
||||||
|
await pennyzoApi.logout(); // Clear any existing token
|
||||||
|
await pennyzoApi.getUserProfile(); // This should fail with 401
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Expected error occurred:');
|
||||||
|
console.error('Status:', error.status);
|
||||||
|
console.error('Message:', error.message);
|
||||||
|
console.error('Error code:', error.code);
|
||||||
|
console.error('Details:', error.details);
|
||||||
|
}
|
||||||
|
}
|
||||||
372
src/services/PennyzoApiService.ts
Normal file
372
src/services/PennyzoApiService.ts
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
|
import type {
|
||||||
|
HealthResponse,
|
||||||
|
RegisterRequest,
|
||||||
|
RegisterResponse,
|
||||||
|
LoginResponse,
|
||||||
|
VerifyEmailResponse,
|
||||||
|
ResendOtpResponse,
|
||||||
|
ForgotPasswordResponse,
|
||||||
|
PasswordResponse,
|
||||||
|
UserMeResponse,
|
||||||
|
UserProfile,
|
||||||
|
UpdateUserProfileRequest,
|
||||||
|
UpdateCompanyRequest,
|
||||||
|
UpdateGstRegistrationRequest,
|
||||||
|
UpgradeMembershipRequest,
|
||||||
|
MembershipInfo,
|
||||||
|
UserStats,
|
||||||
|
PennyzoApiError,
|
||||||
|
ErrorSchema
|
||||||
|
} from '../types/api/pennyzo-api-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PennyzoApiService - A comprehensive TypeScript class for handling all API interactions
|
||||||
|
* with the Pennyzo General API
|
||||||
|
*/
|
||||||
|
export class PennyzoApiService {
|
||||||
|
private apiClient: AxiosInstance;
|
||||||
|
private accessToken: string | null = null;
|
||||||
|
private readonly TOKEN_STORAGE_KEY = 'pennyzo_access_token';
|
||||||
|
private readonly BASE_URL = 'https://api.pennyzo.com/api/v1/general';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Create Axios instance with base configuration
|
||||||
|
this.apiClient = axios.create({
|
||||||
|
baseURL: this.BASE_URL,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load token from storage if available
|
||||||
|
this.loadTokenFromStorage();
|
||||||
|
|
||||||
|
// Setup request interceptor for authentication
|
||||||
|
this.setupRequestInterceptor();
|
||||||
|
|
||||||
|
// Setup response interceptor for error handling
|
||||||
|
this.setupResponseInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the request interceptor to attach the auth token to protected API calls
|
||||||
|
*/
|
||||||
|
private setupRequestInterceptor(): void {
|
||||||
|
this.apiClient.interceptors.request.use((config) => {
|
||||||
|
// If we have a token, add it to the request header
|
||||||
|
if (this.accessToken) {
|
||||||
|
config.headers.Authorization = `Bearer ${this.accessToken}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the response interceptor to handle errors consistently
|
||||||
|
*/
|
||||||
|
private setupResponseInterceptor(): void {
|
||||||
|
this.apiClient.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error: AxiosError<ErrorSchema>) => {
|
||||||
|
const apiError = this.createApiError(error);
|
||||||
|
|
||||||
|
// Handle 401 Unauthorized - Clear token and trigger authentication flow
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
this.clearToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(apiError);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a standardized error object from API errors
|
||||||
|
*/
|
||||||
|
private createApiError(error: AxiosError<ErrorSchema>): PennyzoApiError {
|
||||||
|
const apiError: PennyzoApiError = new Error(
|
||||||
|
error.response?.data?.error?.message || error.message
|
||||||
|
);
|
||||||
|
|
||||||
|
apiError.name = 'PennyzoApiError';
|
||||||
|
apiError.status = error.response?.status;
|
||||||
|
apiError.code = error.response?.data?.error?.type;
|
||||||
|
apiError.details = error.response?.data?.error?.details;
|
||||||
|
apiError.originalError = error;
|
||||||
|
|
||||||
|
return apiError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the authentication token from local storage
|
||||||
|
*/
|
||||||
|
private loadTokenFromStorage(): void {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const token = localStorage.getItem(this.TOKEN_STORAGE_KEY);
|
||||||
|
if (token) {
|
||||||
|
this.accessToken = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the authentication token in local storage
|
||||||
|
*/
|
||||||
|
private storeToken(token: string): void {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(this.TOKEN_STORAGE_KEY, token);
|
||||||
|
this.accessToken = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the authentication token from local storage and memory
|
||||||
|
*/
|
||||||
|
private clearToken(): void {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.removeItem(this.TOKEN_STORAGE_KEY);
|
||||||
|
}
|
||||||
|
this.accessToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== SYSTEM & REGISTRATION METHODS (PUBLIC) ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the health status of the API
|
||||||
|
* GET /system/health
|
||||||
|
*/
|
||||||
|
async checkHealth(): Promise<HealthResponse> {
|
||||||
|
const response = await this.apiClient.get<HealthResponse>('/system/health');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new user
|
||||||
|
* POST /registration/register
|
||||||
|
*/
|
||||||
|
async registerUser(registrationData: RegisterRequest): Promise<RegisterResponse> {
|
||||||
|
const response = await this.apiClient.post<RegisterResponse>(
|
||||||
|
'/registration/register',
|
||||||
|
registrationData
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== AUTHENTICATION & VERIFICATION METHODS (PUBLIC) ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login with email and password
|
||||||
|
* POST /auth/login
|
||||||
|
* Content-Type: application/x-www-form-urlencoded
|
||||||
|
*/
|
||||||
|
async login(email: string, password: string): Promise<LoginResponse> {
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append('username', email); // API uses username field for email
|
||||||
|
formData.append('password', password);
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await this.apiClient.post<LoginResponse>(
|
||||||
|
'/auth/login',
|
||||||
|
formData,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store the token
|
||||||
|
this.storeToken(response.data.access_token);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify user's email with OTP
|
||||||
|
* POST /auth/verify-email
|
||||||
|
*/
|
||||||
|
async verifyEmail(email: string, otp_code: string): Promise<VerifyEmailResponse> {
|
||||||
|
const response = await this.apiClient.post<VerifyEmailResponse>(
|
||||||
|
'/auth/verify-email',
|
||||||
|
{ email, otp_code }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resend verification OTP to email
|
||||||
|
* POST /auth/resend-otp
|
||||||
|
*/
|
||||||
|
async resendVerificationOtp(email: string): Promise<ResendOtpResponse> {
|
||||||
|
const response = await this.apiClient.post<ResendOtpResponse>(
|
||||||
|
'/auth/resend-otp',
|
||||||
|
{ email }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== PASSWORD MANAGEMENT METHODS (PUBLIC/AUTHENTICATED) ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a password reset (forgot password)
|
||||||
|
* POST /auth/forgot-password
|
||||||
|
*/
|
||||||
|
async forgotPassword(email: string): Promise<ForgotPasswordResponse> {
|
||||||
|
const response = await this.apiClient.post<ForgotPasswordResponse>(
|
||||||
|
'/auth/forgot-password',
|
||||||
|
{ email }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset password using OTP
|
||||||
|
* POST /auth/reset-password
|
||||||
|
*/
|
||||||
|
async resetPasswordWithOtp(
|
||||||
|
email: string,
|
||||||
|
otp_code: string,
|
||||||
|
new_password: string
|
||||||
|
): Promise<PasswordResponse> {
|
||||||
|
const response = await this.apiClient.post<PasswordResponse>(
|
||||||
|
'/auth/reset-password',
|
||||||
|
{ email, otp_code, new_password }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change password (requires authentication)
|
||||||
|
* POST /auth/change-password
|
||||||
|
*/
|
||||||
|
async changePassword(
|
||||||
|
current_password: string,
|
||||||
|
new_password: string
|
||||||
|
): Promise<PasswordResponse> {
|
||||||
|
const response = await this.apiClient.post<PasswordResponse>(
|
||||||
|
'/auth/change-password',
|
||||||
|
{ current_password, new_password }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== USER & PROFILE MANAGEMENT METHODS (AUTHENTICATED) ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user information
|
||||||
|
* GET /auth/me
|
||||||
|
*/
|
||||||
|
async getCurrentUser(): Promise<UserMeResponse> {
|
||||||
|
const response = await this.apiClient.get<UserMeResponse>('/auth/me');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user profile details
|
||||||
|
* GET /users/profile
|
||||||
|
*/
|
||||||
|
async getUserProfile(): Promise<UserProfile> {
|
||||||
|
const response = await this.apiClient.get<UserProfile>('/users/profile');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user profile information
|
||||||
|
* PUT /users/profile
|
||||||
|
*/
|
||||||
|
async updateUserProfile(profileUpdateData: UpdateUserProfileRequest): Promise<UserProfile> {
|
||||||
|
const response = await this.apiClient.put<UserProfile>(
|
||||||
|
'/users/profile',
|
||||||
|
profileUpdateData
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update company information
|
||||||
|
* PUT /users/company
|
||||||
|
*/
|
||||||
|
async updateCompanyInfo(companyUpdateData: UpdateCompanyRequest): Promise<UserProfile> {
|
||||||
|
const response = await this.apiClient.put<UserProfile>(
|
||||||
|
'/users/company',
|
||||||
|
companyUpdateData
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update GST registration information
|
||||||
|
* PUT /users/gst-registration
|
||||||
|
*/
|
||||||
|
async updateGstRegistration(gstUpdateData: UpdateGstRegistrationRequest): Promise<UserProfile> {
|
||||||
|
const response = await this.apiClient.put<UserProfile>(
|
||||||
|
'/users/gst-registration',
|
||||||
|
gstUpdateData
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== MEMBERSHIP & STATS METHODS (AUTHENTICATED) ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade user membership
|
||||||
|
* POST /users/upgrade-membership
|
||||||
|
*/
|
||||||
|
async upgradeMembership(membershipData: UpgradeMembershipRequest): Promise<UserProfile> {
|
||||||
|
const response = await this.apiClient.post<UserProfile>(
|
||||||
|
'/users/upgrade-membership',
|
||||||
|
membershipData
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get membership information
|
||||||
|
* GET /users/membership-info
|
||||||
|
*/
|
||||||
|
async getMembershipInfo(): Promise<MembershipInfo> {
|
||||||
|
const response = await this.apiClient.get<MembershipInfo>('/users/membership-info');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user statistics
|
||||||
|
* GET /users/stats
|
||||||
|
*/
|
||||||
|
async getUserStats(): Promise<UserStats> {
|
||||||
|
const response = await this.apiClient.get<UserStats>('/users/stats');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== SESSION MANAGEMENT ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout user by clearing the access token
|
||||||
|
*/
|
||||||
|
logout(): void {
|
||||||
|
this.clearToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is currently authenticated
|
||||||
|
*/
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return !!this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current access token (if any)
|
||||||
|
*/
|
||||||
|
getAccessToken(): string | null {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a singleton instance for easy import and usage
|
||||||
|
export const pennyzoApi = new PennyzoApiService();
|
||||||
|
|
||||||
|
// Default export for convenient import
|
||||||
|
export default PennyzoApiService;
|
||||||
177
src/services/README.md
Normal file
177
src/services/README.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Pennyzo API Service
|
||||||
|
|
||||||
|
A comprehensive TypeScript class for handling all API interactions with the Pennyzo General API.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Complete JWT authentication lifecycle management
|
||||||
|
- Automatic token handling for authenticated requests using Axios interceptors
|
||||||
|
- Comprehensive error handling
|
||||||
|
- TypeScript interfaces for request bodies and response payloads
|
||||||
|
- Singleton pattern for easy usage throughout the application
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The service is already part of the Pennyzo Web project. It requires Axios, which is installed as a project dependency.
|
||||||
|
|
||||||
|
## API Methods
|
||||||
|
|
||||||
|
### System & Registration (Public)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Check health status of the API
|
||||||
|
const healthStatus = await pennyzoApi.checkHealth();
|
||||||
|
|
||||||
|
// Register a new user
|
||||||
|
const registeredUser = await pennyzoApi.registerUser({
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
email: "john.doe@example.com",
|
||||||
|
// other required fields...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication & Verification (Public)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Login a user
|
||||||
|
const loginResult = await pennyzoApi.login("user@example.com", "password123");
|
||||||
|
|
||||||
|
// Verify email with OTP
|
||||||
|
const verificationResult = await pennyzoApi.verifyEmail(
|
||||||
|
"user@example.com",
|
||||||
|
"123456"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Resend verification OTP
|
||||||
|
const resendResult = await pennyzoApi.resendVerificationOtp("user@example.com");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Password Management (Public/Authenticated)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Request password reset
|
||||||
|
const forgotPasswordResult = await pennyzoApi.forgotPassword(
|
||||||
|
"user@example.com"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset password with OTP
|
||||||
|
const resetResult = await pennyzoApi.resetPasswordWithOtp(
|
||||||
|
"user@example.com",
|
||||||
|
"123456",
|
||||||
|
"newPassword123"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Change password (authenticated)
|
||||||
|
const changePasswordResult = await pennyzoApi.changePassword(
|
||||||
|
"currentPassword123",
|
||||||
|
"newPassword123"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### User & Profile Management (Authenticated)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get current user information
|
||||||
|
const currentUser = await pennyzoApi.getCurrentUser();
|
||||||
|
|
||||||
|
// Get user profile
|
||||||
|
const userProfile = await pennyzoApi.getUserProfile();
|
||||||
|
|
||||||
|
// Update user profile
|
||||||
|
const updatedProfile = await pennyzoApi.updateUserProfile({
|
||||||
|
first_name: "Updated",
|
||||||
|
last_name: "Name",
|
||||||
|
// other fields to update
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update company information
|
||||||
|
const updatedCompany = await pennyzoApi.updateCompanyInfo({
|
||||||
|
company_name: "New Company Name",
|
||||||
|
industry: "Technology",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update GST registration
|
||||||
|
const updatedGst = await pennyzoApi.updateGstRegistration({
|
||||||
|
gst_registered: true,
|
||||||
|
gst_number: "GSTIN12345678",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Membership & Stats (Authenticated)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Upgrade membership
|
||||||
|
const upgradedMembership = await pennyzoApi.upgradeMembership({
|
||||||
|
plan_type: "ELITE",
|
||||||
|
membership_type: "PAID",
|
||||||
|
billing_cycle: "yearly",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get membership information
|
||||||
|
const membershipInfo = await pennyzoApi.getMembershipInfo();
|
||||||
|
|
||||||
|
// Get user statistics
|
||||||
|
const userStats = await pennyzoApi.getUserStats();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Logout user (clears stored token)
|
||||||
|
pennyzoApi.logout();
|
||||||
|
|
||||||
|
// Check if user is authenticated
|
||||||
|
const isAuthenticated = pennyzoApi.isAuthenticated();
|
||||||
|
|
||||||
|
// Get the current access token (if any)
|
||||||
|
const accessToken = pennyzoApi.getAccessToken();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The service handles errors gracefully and provides meaningful error objects with details:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const profile = await pennyzoApi.getUserProfile();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error status:", error.status);
|
||||||
|
console.error("Error message:", error.message);
|
||||||
|
console.error("Error code:", error.code);
|
||||||
|
console.error("Error details:", error.details);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
- Uses Axios for all HTTP requests
|
||||||
|
- Base URL is set to `https://api.pennyzo.com/api/v1/general`
|
||||||
|
- Automatically attaches Bearer token to authenticated requests
|
||||||
|
- Handles token storage in localStorage
|
||||||
|
- Special handling for form-urlencoded content type (login endpoint)
|
||||||
|
- Automatic 401 error handling to clear invalid tokens
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Import the singleton instance
|
||||||
|
import { pennyzoApi } from "../services";
|
||||||
|
|
||||||
|
// Use in async/await context
|
||||||
|
async function fetchUserData() {
|
||||||
|
try {
|
||||||
|
// First authenticate
|
||||||
|
await pennyzoApi.login("user@example.com", "password");
|
||||||
|
|
||||||
|
// Then make authenticated calls
|
||||||
|
const profile = await pennyzoApi.getUserProfile();
|
||||||
|
const stats = await pennyzoApi.getUserStats();
|
||||||
|
|
||||||
|
return { profile, stats };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error:", error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
2
src/services/index.ts
Normal file
2
src/services/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { PennyzoApiService, pennyzoApi } from './PennyzoApiService';
|
||||||
|
export { default } from './PennyzoApiService';
|
||||||
85
src/types/api/pennyzo-api-types.ts
Normal file
85
src/types/api/pennyzo-api-types.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// API Response and Request types for Pennyzo API Service
|
||||||
|
import type { RegisterRequest, RegisterResponse,
|
||||||
|
VerifyEmailRequest, VerifyEmailResponse,
|
||||||
|
ResendOtpRequest, ResendOtpResponse,
|
||||||
|
LoginResponse, UserMeResponse,
|
||||||
|
UpdateCompanyRequest, UpdateGstRegistrationRequest,
|
||||||
|
UpdateUserProfileRequest, UpgradeMembershipRequest,
|
||||||
|
UserProfile, ErrorSchema, HealthResponse } from '../api';
|
||||||
|
|
||||||
|
// System & Registration Types
|
||||||
|
export type { HealthResponse, RegisterRequest, RegisterResponse };
|
||||||
|
|
||||||
|
// Authentication & Verification Types
|
||||||
|
export type { LoginResponse, VerifyEmailRequest, VerifyEmailResponse, ResendOtpRequest, ResendOtpResponse, UserMeResponse };
|
||||||
|
|
||||||
|
// Password Management Types
|
||||||
|
export interface ForgotPasswordRequest {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ForgotPasswordResponse {
|
||||||
|
message: string;
|
||||||
|
email: string;
|
||||||
|
expires_in: number;
|
||||||
|
next_step?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResetPasswordRequest {
|
||||||
|
email: string;
|
||||||
|
otp_code: string;
|
||||||
|
new_password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChangePasswordRequest {
|
||||||
|
current_password: string;
|
||||||
|
new_password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PasswordResponse {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User & Profile Management Types
|
||||||
|
export type { UserProfile, UpdateUserProfileRequest, UpdateCompanyRequest, UpdateGstRegistrationRequest };
|
||||||
|
|
||||||
|
// Membership & Stats Types
|
||||||
|
export type { UpgradeMembershipRequest };
|
||||||
|
|
||||||
|
export interface MembershipInfo {
|
||||||
|
id: number;
|
||||||
|
user_id: number;
|
||||||
|
membership_type: string;
|
||||||
|
plan_type: string;
|
||||||
|
is_paid: boolean;
|
||||||
|
plan_start?: string;
|
||||||
|
plan_end?: string;
|
||||||
|
auto_renew?: boolean;
|
||||||
|
payment_method_id?: string;
|
||||||
|
billing_cycle?: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserStats {
|
||||||
|
user_id: number;
|
||||||
|
total_transactions: number;
|
||||||
|
total_amount: number;
|
||||||
|
current_month_transactions: number;
|
||||||
|
current_month_amount: number;
|
||||||
|
pending_invoices: number;
|
||||||
|
pending_payments: number;
|
||||||
|
document_count: number;
|
||||||
|
last_activity_date: string;
|
||||||
|
// Add other stats as per API response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error Types
|
||||||
|
export type { ErrorSchema };
|
||||||
|
|
||||||
|
export interface PennyzoApiError extends Error {
|
||||||
|
status?: number;
|
||||||
|
code?: string;
|
||||||
|
details?: any;
|
||||||
|
originalError?: any;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user