mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
feat: initialize finance project with Fastify, Prisma, and Zod
- Added package.json with necessary dependencies and scripts for development and production. - Created Prisma schema for BankAccount model with fields: id, name, bankName, accountNumber, createdAt, and updatedAt. - Implemented Fastify server with CRUD operations for bank accounts, including validation using Zod. - Added graceful shutdown handling for server and Prisma client.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/overview.md
|
node_modules
|
||||||
|
# Keep environment variables out of version control
|
||||||
|
.env
|
||||||
|
|||||||
3
api/.gitignore
vendored
3
api/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
# Keep environment variables out of version control
|
|
||||||
.env
|
|
||||||
651
api/package-lock.json
generated
651
api/package-lock.json
generated
@@ -1,651 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "finance-api",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "finance-api",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"fastify-type-provider-zod": "^4.0.2",
|
|
||||||
"zod": "^3.24.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fastify/ajv-compiler": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"ajv": "^8.12.0",
|
|
||||||
"ajv-formats": "^3.0.1",
|
|
||||||
"fast-uri": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fastify/error": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-KeFcciOr1eo/YvIXHP65S94jfEEqn1RxTRBT1aJaHxY5FK0/GDXYozsQMMWlZoHgi8i0s+YtrLsgj/JkUUjSkQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@fastify/fast-json-stringify-compiler": {
|
|
||||||
"version": "5.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.2.tgz",
|
|
||||||
"integrity": "sha512-YdR7gqlLg1xZAQa+SX4sMNzQHY5pC54fu9oC5aYSUqBhyn6fkLkrdtKlpVdCNPlwuUuXA1PjFTEmvMF6ZVXVGw==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"fast-json-stringify": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fastify/forwarded": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/@fastify/merge-json-schemas": {
|
|
||||||
"version": "0.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz",
|
|
||||||
"integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"dequal": "^2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fastify/proxy-addr": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/forwarded": "^3.0.0",
|
|
||||||
"ipaddr.js": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/abstract-logging": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/ajv": {
|
|
||||||
"version": "8.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.3",
|
|
||||||
"fast-uri": "^3.0.1",
|
|
||||||
"json-schema-traverse": "^1.0.0",
|
|
||||||
"require-from-string": "^2.0.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ajv-formats": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"ajv": "^8.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"ajv": "^8.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"ajv": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/atomic-sleep": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/avvio": {
|
|
||||||
"version": "9.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz",
|
|
||||||
"integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/error": "^4.0.0",
|
|
||||||
"fastq": "^1.17.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cookie": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dequal": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-decode-uri-component": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/fast-deep-equal": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/fast-json-stringify": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/merge-json-schemas": "^0.2.0",
|
|
||||||
"ajv": "^8.12.0",
|
|
||||||
"ajv-formats": "^3.0.1",
|
|
||||||
"fast-uri": "^3.0.0",
|
|
||||||
"json-schema-ref-resolver": "^2.0.0",
|
|
||||||
"rfdc": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-querystring": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"fast-decode-uri-component": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-redact": {
|
|
||||||
"version": "3.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
|
|
||||||
"integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-uri": {
|
|
||||||
"version": "3.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
|
||||||
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/fastify": {
|
|
||||||
"version": "5.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.2.2.tgz",
|
|
||||||
"integrity": "sha512-22T/PnhquWozuFXg3Ish4md5ipsF1Nx1mJ9ulLdZPXSk14WFj/wMlyNB/yll9sQOojKRgOIxT2inK3Xpjg5hyw==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/ajv-compiler": "^4.0.0",
|
|
||||||
"@fastify/error": "^4.0.0",
|
|
||||||
"@fastify/fast-json-stringify-compiler": "^5.0.0",
|
|
||||||
"@fastify/proxy-addr": "^5.0.0",
|
|
||||||
"abstract-logging": "^2.0.1",
|
|
||||||
"avvio": "^9.0.0",
|
|
||||||
"fast-json-stringify": "^6.0.0",
|
|
||||||
"find-my-way": "^9.0.0",
|
|
||||||
"light-my-request": "^6.0.0",
|
|
||||||
"pino": "^9.0.0",
|
|
||||||
"process-warning": "^4.0.0",
|
|
||||||
"rfdc": "^1.3.1",
|
|
||||||
"secure-json-parse": "^3.0.1",
|
|
||||||
"semver": "^7.6.0",
|
|
||||||
"toad-cache": "^3.7.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fastify-type-provider-zod": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fastify-type-provider-zod/-/fastify-type-provider-zod-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-FDRzSL3ZuoZ+4YDevR1YOinmDKkxOdy3QB9dDR845sK+bQvDroPKhHAXLEAOObDxL7SMA0OZN/A4osrNBTdDTQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/error": "^4.0.0",
|
|
||||||
"zod-to-json-schema": "^3.23.3"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"fastify": "^5.0.0",
|
|
||||||
"zod": "^3.14.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fastq": {
|
|
||||||
"version": "1.19.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
|
||||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"reusify": "^1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/find-my-way": {
|
|
||||||
"version": "9.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz",
|
|
||||||
"integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.3",
|
|
||||||
"fast-querystring": "^1.0.0",
|
|
||||||
"safe-regex2": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ipaddr.js": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-resolver": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"dequal": "^2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-traverse": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/light-my-request": {
|
|
||||||
"version": "6.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz",
|
|
||||||
"integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"cookie": "^1.0.1",
|
|
||||||
"process-warning": "^4.0.0",
|
|
||||||
"set-cookie-parser": "^2.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/on-exit-leak-free": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pino": {
|
|
||||||
"version": "9.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz",
|
|
||||||
"integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"atomic-sleep": "^1.0.0",
|
|
||||||
"fast-redact": "^3.1.1",
|
|
||||||
"on-exit-leak-free": "^2.1.0",
|
|
||||||
"pino-abstract-transport": "^2.0.0",
|
|
||||||
"pino-std-serializers": "^7.0.0",
|
|
||||||
"process-warning": "^4.0.0",
|
|
||||||
"quick-format-unescaped": "^4.0.3",
|
|
||||||
"real-require": "^0.2.0",
|
|
||||||
"safe-stable-stringify": "^2.3.1",
|
|
||||||
"sonic-boom": "^4.0.1",
|
|
||||||
"thread-stream": "^3.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"pino": "bin.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pino-abstract-transport": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"split2": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pino-std-serializers": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/process-warning": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/quick-format-unescaped": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/real-require": {
|
|
||||||
"version": "0.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
|
||||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.13.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/require-from-string": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ret": {
|
|
||||||
"version": "0.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz",
|
|
||||||
"integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reusify": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"iojs": ">=1.0.0",
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rfdc": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/safe-regex2": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"ret": "~0.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/safe-stable-stringify": {
|
|
||||||
"version": "2.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
|
||||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/secure-json-parse": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/fastify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fastify"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
|
||||||
"version": "7.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
|
||||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/set-cookie-parser": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/sonic-boom": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"atomic-sleep": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/split2": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/thread-stream": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"real-require": "^0.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/toad-cache": {
|
|
||||||
"version": "3.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz",
|
|
||||||
"integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/zod": {
|
|
||||||
"version": "3.24.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
|
||||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/zod-to-json-schema": {
|
|
||||||
"version": "3.24.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
|
|
||||||
"integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.24.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "finance-api",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"start": "node dist/server.js",
|
|
||||||
"dev": "nodemon --watch src --ext ts --exec ts-node src/server.ts",
|
|
||||||
"prisma:migrate": "prisma migrate dev",
|
|
||||||
"prisma:generate": "prisma generate",
|
|
||||||
"prisma:studio": "prisma studio"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "Peter Wood <peter@peterwood.dev>",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": "",
|
|
||||||
"dependencies": {
|
|
||||||
"fastify-type-provider-zod": "^4.0.2",
|
|
||||||
"zod": "^3.24.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
-- CreateTable
|
|
||||||
CREATE TABLE "bank_accounts" (
|
|
||||||
"id" SERIAL NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"bankName" TEXT NOT NULL,
|
|
||||||
"accountNumber" TEXT NOT NULL,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "bank_accounts_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "bank_accounts_accountNumber_key" ON "bank_accounts"("accountNumber");
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (e.g., Git)
|
|
||||||
provider = "postgresql"
|
|
||||||
9
bank_account.http
Normal file
9
bank_account.http
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
POST /api/bank-account/update/2 HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
|
Host: europa:3050
|
||||||
|
Content-Length: 85
|
||||||
|
|
||||||
|
{"name": "BofA Joint Checking","bankName": "Bank of America","accountNumber": "4581"}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
411
overview.md
Normal file
411
overview.md
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
Okay, let's set up a skeleton Fastify API project with Prisma to interact with a PostgreSQL database. This structure will provide the requested endpoints.
|
||||||
|
|
||||||
|
**Assumptions:**
|
||||||
|
|
||||||
|
1. You have Node.js and npm (or yarn) installed.
|
||||||
|
2. You have PostgreSQL installed and running locally.
|
||||||
|
3. You have a PostgreSQL database created (e.g., `finance_db`).
|
||||||
|
4. You have a PostgreSQL user with privileges on that database.
|
||||||
|
|
||||||
|
**Project Setup Steps:**
|
||||||
|
|
||||||
|
1. **Create Project Directory & Initialize:**
|
||||||
|
```bash
|
||||||
|
mkdir finance-api
|
||||||
|
cd finance-api
|
||||||
|
npm init -y
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Dependencies:**
|
||||||
|
```bash
|
||||||
|
# Runtime dependencies
|
||||||
|
npm install fastify @prisma/client dotenv
|
||||||
|
|
||||||
|
# Development dependencies
|
||||||
|
npm install --save-dev prisma typescript @types/node ts-node nodemon
|
||||||
|
# Or if you prefer plain Javascript, skip typescript/ts-node and adjust scripts
|
||||||
|
```
|
||||||
|
* `fastify`: The web framework.
|
||||||
|
* `@prisma/client`: The Prisma database client.
|
||||||
|
* `dotenv`: To load environment variables from a `.env` file.
|
||||||
|
* `prisma`: The Prisma CLI (for migrations, generation).
|
||||||
|
* `typescript`, `@types/node`, `ts-node`: For TypeScript support (recommended).
|
||||||
|
* `nodemon`: To automatically restart the server during development.
|
||||||
|
|
||||||
|
3. **Initialize Prisma:**
|
||||||
|
```bash
|
||||||
|
npx prisma init --datasource-provider postgresql
|
||||||
|
```
|
||||||
|
* This creates a `prisma` directory with a `schema.prisma` file and a `.env` file.
|
||||||
|
|
||||||
|
4. **Configure `.env`:**
|
||||||
|
Edit the newly created `.env` file and set your PostgreSQL connection URL:
|
||||||
|
```dotenv
|
||||||
|
# .env
|
||||||
|
# Replace with your actual database user, password, host, port, and database name
|
||||||
|
DATABASE_URL="postgresql://YOUR_USER:YOUR_PASSWORD@localhost:5432/finance_db?schema=public"
|
||||||
|
|
||||||
|
# API Server Configuration (Optional, but good practice)
|
||||||
|
API_HOST=0.0.0.0
|
||||||
|
API_PORT=3050
|
||||||
|
API_BASE_URL=https://finance.ptrwd.com # Used for documentation/reference, not binding
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Define Prisma Schema (`prisma/schema.prisma`):**
|
||||||
|
Define the model for your bank accounts.
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
// prisma/schema.prisma
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model BankAccount {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String // e.g., "Checking Account", "Savings XYZ"
|
||||||
|
bankName String // e.g., "Chase", "Wells Fargo"
|
||||||
|
accountNumber String @unique // Consider encryption in a real app
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@map("bank_accounts") // Optional: specify table name in snake_case
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Create Initial Database Migration:**
|
||||||
|
```bash
|
||||||
|
npx prisma migrate dev --name init_bank_account
|
||||||
|
```
|
||||||
|
* This command does two things:
|
||||||
|
* Creates an SQL migration file in `prisma/migrations`.
|
||||||
|
* Applies the migration to your database, creating the `bank_accounts` table.
|
||||||
|
|
||||||
|
7. **Generate Prisma Client:**
|
||||||
|
Ensure the client is generated based on your schema:
|
||||||
|
```bash
|
||||||
|
npx prisma generate
|
||||||
|
```
|
||||||
|
* This command reads your `schema.prisma` and generates the typed `@prisma/client`. You typically run this after any schema change.
|
||||||
|
|
||||||
|
8. **Create the Fastify Server (`src/server.ts` or `server.js`):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/server.ts (if using TypeScript)
|
||||||
|
// If using Javascript, remove type annotations and use require instead of import
|
||||||
|
|
||||||
|
import Fastify, { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
||||||
|
import { Server, IncomingMessage, ServerResponse } from 'http';
|
||||||
|
import { PrismaClient, BankAccount } from '@prisma/client';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const server: FastifyInstance = Fastify({
|
||||||
|
logger: true // Enable logging
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Type Definitions for Payloads/Params (Good Practice) ---
|
||||||
|
interface BankAccountCreateParams {
|
||||||
|
name: string;
|
||||||
|
bankName: string;
|
||||||
|
accountNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BankAccountUpdateParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
interface BankAccountUpdateBody {
|
||||||
|
name?: string;
|
||||||
|
bankName?: string;
|
||||||
|
accountNumber?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BankAccountDeleteParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- API Routes ---
|
||||||
|
const API_PREFIX = '/api/bank-account';
|
||||||
|
|
||||||
|
// 1. Create Bank Account
|
||||||
|
server.post<{ Body: BankAccountCreateParams }>(
|
||||||
|
`${API_PREFIX}/create`,
|
||||||
|
async (request, reply): Promise<BankAccount> => {
|
||||||
|
try {
|
||||||
|
const { name, bankName, accountNumber } = request.body;
|
||||||
|
const newAccount = await prisma.bankAccount.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
bankName,
|
||||||
|
accountNumber,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
reply.code(201); // Resource Created
|
||||||
|
return newAccount;
|
||||||
|
} catch (error: any) {
|
||||||
|
server.log.error(error);
|
||||||
|
// Basic duplicate check example (Prisma throws P2002)
|
||||||
|
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
||||||
|
reply.code(409); // Conflict
|
||||||
|
throw new Error(`Bank account with number ${request.body.accountNumber} already exists.`);
|
||||||
|
}
|
||||||
|
reply.code(500);
|
||||||
|
throw new Error('Failed to create bank account.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Update Bank Account
|
||||||
|
server.post<{ Params: BankAccountUpdateParams; Body: BankAccountUpdateBody }>(
|
||||||
|
`${API_PREFIX}/update/:id`,
|
||||||
|
async (request, reply): Promise<BankAccount> => {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const updateData = request.body;
|
||||||
|
|
||||||
|
// Ensure ID is a valid number before querying
|
||||||
|
const accountId = parseInt(id, 10);
|
||||||
|
if (isNaN(accountId)) {
|
||||||
|
reply.code(400);
|
||||||
|
throw new Error('Invalid account ID format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedAccount = await prisma.bankAccount.update({
|
||||||
|
where: { id: accountId },
|
||||||
|
data: updateData,
|
||||||
|
});
|
||||||
|
return updatedAccount;
|
||||||
|
} catch (error: any) {
|
||||||
|
server.log.error(error);
|
||||||
|
// Handle case where account doesn't exist (Prisma throws P2025)
|
||||||
|
if (error.code === 'P2025') {
|
||||||
|
reply.code(404); // Not Found
|
||||||
|
throw new Error(`Bank account with ID ${request.params.id} not found.`);
|
||||||
|
}
|
||||||
|
// Handle potential duplicate account number on update
|
||||||
|
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
||||||
|
reply.code(409); // Conflict
|
||||||
|
throw new Error(`Bank account with number ${request.body.accountNumber} already exists.`);
|
||||||
|
}
|
||||||
|
reply.code(500);
|
||||||
|
throw new Error('Failed to update bank account.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Delete Bank Account
|
||||||
|
server.delete<{ Params: BankAccountDeleteParams }>(
|
||||||
|
`${API_PREFIX}/delete/:id`,
|
||||||
|
async (request, reply): Promise<{ message: string; deletedAccount: BankAccount }> => {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
// Ensure ID is a valid number
|
||||||
|
const accountId = parseInt(id, 10);
|
||||||
|
if (isNaN(accountId)) {
|
||||||
|
reply.code(400);
|
||||||
|
throw new Error('Invalid account ID format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedAccount = await prisma.bankAccount.delete({
|
||||||
|
where: { id: accountId },
|
||||||
|
});
|
||||||
|
return { message: `Bank account with ID ${id} deleted successfully.`, deletedAccount };
|
||||||
|
} catch (error: any) {
|
||||||
|
server.log.error(error);
|
||||||
|
// Handle case where account doesn't exist (Prisma throws P2025)
|
||||||
|
if (error.code === 'P2025') {
|
||||||
|
reply.code(404); // Not Found
|
||||||
|
throw new Error(`Bank account with ID ${request.params.id} not found.`);
|
||||||
|
}
|
||||||
|
reply.code(500);
|
||||||
|
throw new Error('Failed to delete bank account.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Get All Bank Accounts
|
||||||
|
server.get(
|
||||||
|
`${API_PREFIX}/`, // Using trailing slash for consistency, Fastify often handles both
|
||||||
|
async (request, reply): Promise<BankAccount[]> => {
|
||||||
|
try {
|
||||||
|
const accounts = await prisma.bankAccount.findMany({
|
||||||
|
orderBy: { // Optional: Order results
|
||||||
|
createdAt: 'desc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return accounts;
|
||||||
|
} catch (error: any) {
|
||||||
|
server.log.error(error);
|
||||||
|
reply.code(500);
|
||||||
|
throw new Error('Failed to retrieve bank accounts.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Optional: Get Single Bank Account by ID (Often useful)
|
||||||
|
server.get<{ Params: { id: string } }>(
|
||||||
|
`${API_PREFIX}/:id`,
|
||||||
|
async (request, reply): Promise<BankAccount> => {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const accountId = parseInt(id, 10);
|
||||||
|
if (isNaN(accountId)) {
|
||||||
|
reply.code(400);
|
||||||
|
throw new Error('Invalid account ID format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await prisma.bankAccount.findUnique({
|
||||||
|
where: { id: accountId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
reply.code(404);
|
||||||
|
throw new Error(`Bank account with ID ${id} not found.`);
|
||||||
|
}
|
||||||
|
return account;
|
||||||
|
} catch (error: any) {
|
||||||
|
// Catch errors that might have been thrown before prisma query
|
||||||
|
if (!reply.sent) { // Check if a reply hasn't already been sent (e.g., by 400 error)
|
||||||
|
server.log.error(error);
|
||||||
|
reply.code(500);
|
||||||
|
throw new Error('Failed to retrieve bank account.');
|
||||||
|
}
|
||||||
|
// If reply already sent, just rethrow the original error
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Graceful Shutdown ---
|
||||||
|
const gracefulShutdown = async (signal: string) => {
|
||||||
|
console.log(`*^! Received signal ${signal}. Shutting down...`);
|
||||||
|
try {
|
||||||
|
await server.close();
|
||||||
|
console.log('Fastify server closed.');
|
||||||
|
await prisma.$disconnect();
|
||||||
|
console.log('Prisma client disconnected.');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error during shutdown:', err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||||
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||||
|
|
||||||
|
|
||||||
|
// --- Start Server ---
|
||||||
|
const start = async () => {
|
||||||
|
try {
|
||||||
|
const host = process.env.API_HOST || '0.0.0.0';
|
||||||
|
const port = parseInt(process.env.API_PORT || '3050', 10);
|
||||||
|
|
||||||
|
await server.listen({ port, host });
|
||||||
|
|
||||||
|
// Log after listen resolves (Fastify v3+)
|
||||||
|
// server.log.info(`Server listening at http://${host}:${port}`);
|
||||||
|
// Note: Fastify logger automatically logs listening address
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
server.log.error(err);
|
||||||
|
await prisma.$disconnect(); // Ensure prisma disconnects on startup error too
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
// Export prisma instance if needed elsewhere (e.g., tests, scripts)
|
||||||
|
// export { prisma };
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Configure `tsconfig.json` (if using TypeScript):**
|
||||||
|
Create a `tsconfig.json` file in the root directory:
|
||||||
|
```json
|
||||||
|
// tsconfig.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017", // Or newer
|
||||||
|
"module": "CommonJS",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true, // Helpful for debugging
|
||||||
|
"resolveJsonModule": true // Allows importing JSON files
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"], // Only compile files in src
|
||||||
|
"exclude": ["node_modules", "**/*.spec.ts"] // Exclude tests if you have them
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
10. **Add Scripts to `package.json`:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json (add or modify the scripts section)
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc", // Compile TypeScript to JavaScript (if using TS)
|
||||||
|
"start": "node dist/server.js", // Run compiled JS (if using TS)
|
||||||
|
"dev": "nodemon --watch src --ext ts --exec ts-node src/server.ts", // Dev mode with TS
|
||||||
|
// --- OR if using plain JavaScript ---
|
||||||
|
// "start": "node server.js",
|
||||||
|
// "dev": "nodemon server.js",
|
||||||
|
// --- Prisma commands for convenience ---
|
||||||
|
"prisma:migrate": "prisma migrate dev",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:studio": "prisma studio" // GUI to view/edit data
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
11. **Run the Development Server:**
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**How to Test (using `curl`):**
|
||||||
|
|
||||||
|
*(Replace `YOUR_ACCOUNT_ID` with an actual ID after creating an account)*
|
||||||
|
|
||||||
|
* **Create:**
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"name": "My Checking", "bankName": "Local Bank", "accountNumber": "123456789"}' \
|
||||||
|
http://localhost:3050/api/bank-account/create
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Get All:**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3050/api/bank-account/
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Update (using the ID returned from create):**
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"name": "My Primary Checking"}' \
|
||||||
|
http://localhost:3050/api/bank-account/update/YOUR_ACCOUNT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Delete (using the ID):**
|
||||||
|
```bash
|
||||||
|
curl -X DELETE http://localhost:3050/api/bank-account/delete/YOUR_ACCOUNT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Get Specific (using the ID):**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3050/api/bank-account/YOUR_ACCOUNT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
This skeleton provides the core structure. You can build upon this by adding more robust error handling, input validation (using Fastify's built-in schema validation), authentication/authorization, more complex queries, and organizing routes into separate files/plugins as the application grows.
|
||||||
1797
package-lock.json
generated
Normal file
1797
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "finance",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node dist/server.js",
|
||||||
|
"dev": "nodemon --watch src --ext ts --exec ts-node src/server.ts",
|
||||||
|
"prisma:migrate": "prisma migrate dev",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:studio": "prisma studio"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/acedanger/finance.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Peter Wood <peter@peterwood.dev>",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/acedanger/finance/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/acedanger/finance#readme",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^6.5.0",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"fastify": "^5.2.2",
|
||||||
|
"fastify-type-provider-zod": "^4.0.2",
|
||||||
|
"zod": "^3.24.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.14.0",
|
||||||
|
"nodemon": "^3.1.9",
|
||||||
|
"prisma": "^6.5.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,28 +4,18 @@ import Fastify, { FastifyInstance } from 'fastify';
|
|||||||
import { Server, IncomingMessage, ServerResponse } from 'http';
|
import { Server, IncomingMessage, ServerResponse } from 'http';
|
||||||
import { PrismaClient, BankAccount } from '@prisma/client';
|
import { PrismaClient, BankAccount } from '@prisma/client';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { z } from 'zod'; // Import Zod
|
import { z } from 'zod';
|
||||||
import { ZodTypeProvider } from 'fastify-type-provider-zod'; // Import the type provider
|
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
||||||
|
|
||||||
// Load environment variables
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
// --- Zod Schema Definitions ---
|
|
||||||
|
|
||||||
// Base schema for common fields, useful for reuse
|
// Base schema for common fields, useful for reuse
|
||||||
const bankAccountBaseSchema = z.object({
|
const bankAccountBaseSchema = z.object({
|
||||||
name: z.string().min(1, { message: "Name cannot be empty" }),
|
name: z.string().min(1, { message: "Name cannot be empty" }),
|
||||||
bankName: z.string().min(1, { message: "Bank name cannot be empty" }),
|
bankName: z.string().min(1, { message: "Bank name cannot be empty" }),
|
||||||
accountNumber: z.string().min(1, { message: "Account number cannot be empty" }),
|
accountNumber: z.string().min(1, { message: "Account number cannot be empty" }),
|
||||||
/*
|
|
||||||
// Use transform or coerce for flexibility with input (string/number) -> number for Prisma Decimal
|
|
||||||
balance: z.preprocess(
|
|
||||||
(val) => (typeof val === 'string' ? parseFloat(val) : val),
|
|
||||||
z.number().min(0, { message: "Balance cannot be negative" })
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schema for creating a bank account (all fields required)
|
// Schema for creating a bank account (all fields required)
|
||||||
@@ -69,7 +59,8 @@ server.post(
|
|||||||
server.log.error(error);
|
server.log.error(error);
|
||||||
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
||||||
reply.code(409);
|
reply.code(409);
|
||||||
throw new Error(`Bank account with number ${request.body.accountNumber} already exists.`);
|
const body = createBankAccountSchema.parse(request.body);
|
||||||
|
throw new Error(`Bank account with number ${body.accountNumber} already exists.`);
|
||||||
}
|
}
|
||||||
reply.code(500);
|
reply.code(500);
|
||||||
throw new Error('Failed to create bank account.');
|
throw new Error('Failed to create bank account.');
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
// tsconfig.json
|
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017", // Or newer
|
"target": "ES2017", // Or newer
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "api/src",
|
"rootDir": "src",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
@@ -13,6 +12,6 @@
|
|||||||
"sourceMap": true, // Helpful for debugging
|
"sourceMap": true, // Helpful for debugging
|
||||||
"resolveJsonModule": true // Allows importing JSON files
|
"resolveJsonModule": true // Allows importing JSON files
|
||||||
},
|
},
|
||||||
"include": ["api/src/**/*"], // Only compile files in src
|
"include": ["src/**/*"], // Only compile files in src
|
||||||
"exclude": ["node_modules", "**/*.spec.ts"] // Exclude tests if you have them
|
"exclude": ["node_modules", "**/*.spec.ts"] // Exclude tests if you have them
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user