From 7c1cf427d127e6201af792741a7d28266a4ab529 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 5 Apr 2025 10:44:18 -0400 Subject: [PATCH 01/16] first commit --- .gitignore | 1 + api/.gitignore | 3 + api/docker-compose.yml | 44 ++ api/package-lock.json | 651 ++++++++++++++++++ api/package.json | 21 + .../migration.sql | 14 + api/prisma/migrations/migration_lock.toml | 3 + api/prisma/schema.prisma | 21 + api/src/server.ts | 255 +++++++ tsconfig.json | 18 + 10 files changed, 1031 insertions(+) create mode 100644 .gitignore create mode 100644 api/.gitignore create mode 100644 api/docker-compose.yml create mode 100644 api/package-lock.json create mode 100644 api/package.json create mode 100644 api/prisma/migrations/20250405121659_init_bank_account/migration.sql create mode 100644 api/prisma/migrations/migration_lock.toml create mode 100644 api/prisma/schema.prisma create mode 100644 api/src/server.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e68ae0c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/overview.md \ No newline at end of file diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..11ddd8d --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Keep environment variables out of version control +.env diff --git a/api/docker-compose.yml b/api/docker-compose.yml new file mode 100644 index 0000000..76b8633 --- /dev/null +++ b/api/docker-compose.yml @@ -0,0 +1,44 @@ +name: finance-api +services: + postgres: + container_name: postgres_container + image: postgres:15 + environment: + POSTGRES_USER: ${POSTGRES_USER:-financeuser} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + POSTGRES_DB: ${POSTGRES_DB:-finance} + PGDATA: /data/postgres + volumes: + - postgres_data:/data/postgres + ports: + - "${POSTGRES_PORT}:5432" + networks: + - postgres + restart: unless-stopped + labels: + - diun.enable=true + + pgadmin: + container_name: pgadmin_container + image: dpage/pgadmin4 + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-peter@peterwood.dev} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} + PGADMIN_CONFIG_SERVER_MODE: 'False' + volumes: + - pgadmin_data:/var/lib/pgadmin + ports: + - "${PGADMIN_PORT:-5050}:80" + networks: + - postgres + restart: unless-stopped + labels: + - diun.enable=true + +networks: + postgres: + driver: bridge + +volumes: + postgres_data: + pgadmin_data: diff --git a/api/package-lock.json b/api/package-lock.json new file mode 100644 index 0000000..4c5f3ff --- /dev/null +++ b/api/package-lock.json @@ -0,0 +1,651 @@ +{ + "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" + } + } + } +} diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..5e9e6dc --- /dev/null +++ b/api/package.json @@ -0,0 +1,21 @@ +{ + "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 ", + "license": "ISC", + "description": "", + "dependencies": { + "fastify-type-provider-zod": "^4.0.2", + "zod": "^3.24.2" + } +} diff --git a/api/prisma/migrations/20250405121659_init_bank_account/migration.sql b/api/prisma/migrations/20250405121659_init_bank_account/migration.sql new file mode 100644 index 0000000..0311767 --- /dev/null +++ b/api/prisma/migrations/20250405121659_init_bank_account/migration.sql @@ -0,0 +1,14 @@ +-- 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"); diff --git a/api/prisma/migrations/migration_lock.toml b/api/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..648c57f --- /dev/null +++ b/api/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" \ No newline at end of file diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma new file mode 100644 index 0000000..5496475 --- /dev/null +++ b/api/prisma/schema.prisma @@ -0,0 +1,21 @@ +// 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 +} diff --git a/api/src/server.ts b/api/src/server.ts new file mode 100644 index 0000000..36e7795 --- /dev/null +++ b/api/src/server.ts @@ -0,0 +1,255 @@ +// src/server.ts + +import Fastify, { FastifyInstance } from 'fastify'; +import { Server, IncomingMessage, ServerResponse } from 'http'; +import { PrismaClient, BankAccount } from '@prisma/client'; +import dotenv from 'dotenv'; +import { z } from 'zod'; // Import Zod +import { ZodTypeProvider } from 'fastify-type-provider-zod'; // Import the type provider + +// Load environment variables +dotenv.config(); + +const prisma = new PrismaClient(); + +// --- Zod Schema Definitions --- + +// Base schema for common fields, useful for reuse +const bankAccountBaseSchema = z.object({ + name: z.string().min(1, { message: "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" }), + /* + // 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) +const createBankAccountSchema = bankAccountBaseSchema; + +// Schema for request parameters containing an ID +const paramsSchema = z.object({ + // Use coerce to automatically convert string param to number + id: z.coerce.number().int().positive({ message: "ID must be a positive integer" }) +}); + +// Schema for updating a bank account (all fields optional) +const updateBankAccountSchema = bankAccountBaseSchema.partial(); // Makes all fields optional + +// --- Fastify Server Instance with Zod Type Provider --- +const server: FastifyInstance = Fastify({ + logger: true +}).withTypeProvider(); // Enable Zod validation and typing + +// --- API Routes --- + +const API_PREFIX = '/api/bank-account'; + +// 1. Create Bank Account +server.post( + `${API_PREFIX}/create`, + { + schema: { // Define Zod schema for the request body + body: createBankAccountSchema + } + }, + async (request, reply): Promise => { + try { + // request.body is now typed and validated by Zod! + const newAccount = await prisma.bankAccount.create({ + data: request.body, // Pass validated body directly + }); + reply.code(201); + return newAccount; + } catch (error: any) { + server.log.error(error); + if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { + reply.code(409); + 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( + `${API_PREFIX}/update/:id`, + { + schema: { // Define Zod schemas for params and body + params: paramsSchema, + body: updateBankAccountSchema + } + }, + async (request, reply): Promise => { + try { + // request.params.id is now a validated number + // request.body is now a validated partial object + const { id } = request.params; + const updateData = request.body; + + // Prevent updating with an empty object + if (Object.keys(updateData).length === 0) { + reply.code(400); + throw new Error("Request body cannot be empty for update."); + } + + const updatedAccount = await prisma.bankAccount.update({ + where: { id: id }, // Use the validated numeric ID + data: updateData, + }); + return updatedAccount; + } catch (error: any) { + server.log.error(error); + if (error.code === 'P2025') { // Record to update not found + reply.code(404); + throw new Error(`Bank account with ID ${request.params.id} not found.`); + } + if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { + reply.code(409); + // Access accountNumber safely as it's optional in update + const attemptedNumber = request.body.accountNumber || '(unchanged)'; + throw new Error(`Bank account with number ${attemptedNumber} already exists.`); + } + // Handle Zod validation errors specifically if needed (though Fastify usually does) + if (error instanceof z.ZodError) { + reply.code(400); + throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`); + } + reply.code(500); + throw new Error('Failed to update bank account.'); + } + } +); + +// 3. Delete Bank Account +server.delete( + `${API_PREFIX}/delete/:id`, + { + schema: { // Define Zod schema for params + params: paramsSchema + } + }, + async (request, reply): Promise<{ message: string; deletedAccount: BankAccount }> => { + try { + // request.params.id is now a validated number + const { id } = request.params; + + const deletedAccount = await prisma.bankAccount.delete({ + where: { id: id }, // Use the validated numeric ID + }); + return { message: `Bank account with ID ${id} deleted successfully.`, deletedAccount }; + } catch (error: any) { + server.log.error(error); + if (error.code === 'P2025') { // Record to delete not found + reply.code(404); + throw new Error(`Bank account with ID ${request.params.id} not found.`); + } + // Handle Zod validation errors + if (error instanceof z.ZodError) { + reply.code(400); + throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`); + } + reply.code(500); + throw new Error('Failed to delete bank account.'); + } + } +); + +// 4. Get All Bank Accounts +server.get( + `${API_PREFIX}/`, + async (request, reply): Promise => { + // No input validation needed for getting all items usually + try { + const accounts = await prisma.bankAccount.findMany({ + orderBy: { 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 +server.get( + `${API_PREFIX}/:id`, + { + schema: { // Define Zod schema for params + params: paramsSchema + } + }, + async (request, reply): Promise => { + try { + // request.params.id is now a validated number + const { id } = request.params; + + const account = await prisma.bankAccount.findUnique({ + where: { id: id }, // Use the validated numeric ID + }); + + if (!account) { + reply.code(404); + throw new Error(`Bank account with ID ${id} not found.`); + } + return account; + } catch (error: any) { + // Handle Zod validation errors (though should be caught by Fastify earlier) + if (error instanceof z.ZodError) { + reply.code(400); + throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`); + } + // If Prisma throws or other errors occur after validation + if (!reply.sent) { + // Specific check for Prisma's RecordNotFound (though findUnique returns null, not throws P2025 by default) + // The !account check above handles the "not found" case for findUnique + + server.log.error(error); // Log other unexpected errors + reply.code(500); + throw new Error('Failed to retrieve bank account.'); + } + // If reply already sent (e.g., 404), 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 (unchanged) --- +const start = async () => { + try { + const host = process.env.API_HOST || '0.0.0.0'; + const port = parseInt(process.env.API_PORT || '3000', 10); + await server.listen({ port, host }); + } catch (err) { + server.log.error(err); + await prisma.$disconnect(); + process.exit(1); + } +}; + +start(); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f0ea559 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +// tsconfig.json +{ + "compilerOptions": { + "target": "ES2017", // Or newer + "module": "CommonJS", + "outDir": "./dist", + "rootDir": "api/src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "sourceMap": true, // Helpful for debugging + "resolveJsonModule": true // Allows importing JSON files + }, + "include": ["api/src/**/*"], // Only compile files in src + "exclude": ["node_modules", "**/*.spec.ts"] // Exclude tests if you have them +} \ No newline at end of file From d150757025deb3a4a2475c0bd71de96aa3117bf0 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 5 Apr 2025 17:52:40 -0400 Subject: [PATCH 02/16] 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. --- .gitignore | 4 +- api/.gitignore | 3 - api/package-lock.json | 651 ------ api/package.json | 21 - .../migration.sql | 14 - api/prisma/migrations/migration_lock.toml | 3 - bank_account.http | 9 + api/docker-compose.yml => docker-compose.yml | 0 overview.md | 411 ++++ package-lock.json | 1797 +++++++++++++++++ package.json | 39 + {api/prisma => prisma}/schema.prisma | 2 +- {api/src => src}/server.ts | 17 +- tsconfig.json | 5 +- 14 files changed, 2266 insertions(+), 710 deletions(-) delete mode 100644 api/.gitignore delete mode 100644 api/package-lock.json delete mode 100644 api/package.json delete mode 100644 api/prisma/migrations/20250405121659_init_bank_account/migration.sql delete mode 100644 api/prisma/migrations/migration_lock.toml create mode 100644 bank_account.http rename api/docker-compose.yml => docker-compose.yml (100%) create mode 100644 overview.md create mode 100644 package-lock.json create mode 100644 package.json rename {api/prisma => prisma}/schema.prisma (99%) rename {api/src => src}/server.ts (93%) diff --git a/.gitignore b/.gitignore index e68ae0c..11ddd8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -/overview.md \ No newline at end of file +node_modules +# Keep environment variables out of version control +.env diff --git a/api/.gitignore b/api/.gitignore deleted file mode 100644 index 11ddd8d..0000000 --- a/api/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -# Keep environment variables out of version control -.env diff --git a/api/package-lock.json b/api/package-lock.json deleted file mode 100644 index 4c5f3ff..0000000 --- a/api/package-lock.json +++ /dev/null @@ -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" - } - } - } -} diff --git a/api/package.json b/api/package.json deleted file mode 100644 index 5e9e6dc..0000000 --- a/api/package.json +++ /dev/null @@ -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 ", - "license": "ISC", - "description": "", - "dependencies": { - "fastify-type-provider-zod": "^4.0.2", - "zod": "^3.24.2" - } -} diff --git a/api/prisma/migrations/20250405121659_init_bank_account/migration.sql b/api/prisma/migrations/20250405121659_init_bank_account/migration.sql deleted file mode 100644 index 0311767..0000000 --- a/api/prisma/migrations/20250405121659_init_bank_account/migration.sql +++ /dev/null @@ -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"); diff --git a/api/prisma/migrations/migration_lock.toml b/api/prisma/migrations/migration_lock.toml deleted file mode 100644 index 648c57f..0000000 --- a/api/prisma/migrations/migration_lock.toml +++ /dev/null @@ -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" \ No newline at end of file diff --git a/bank_account.http b/bank_account.http new file mode 100644 index 0000000..4e5077e --- /dev/null +++ b/bank_account.http @@ -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"} + +### + diff --git a/api/docker-compose.yml b/docker-compose.yml similarity index 100% rename from api/docker-compose.yml rename to docker-compose.yml diff --git a/overview.md b/overview.md new file mode 100644 index 0000000..0af4a20 --- /dev/null +++ b/overview.md @@ -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 => { + 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 => { + 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 => { + 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 => { + 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. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0f75817 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1797 @@ +{ + "name": "finance", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "finance", + "version": "1.0.0", + "license": "ISC", + "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" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "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", + "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" + }, + "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", + "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", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@prisma/client": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.5.0.tgz", + "integrity": "sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.5.0.tgz", + "integrity": "sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "esbuild": ">=0.12 <1", + "esbuild-register": "3.6.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.5.0.tgz", + "integrity": "sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.5.0.tgz", + "integrity": "sha512-FVPQYHgOllJklN9DUyujXvh3hFJCY0NX86sDmBErLvoZjy2OXGiZ5FNf3J/C4/RZZmCypZBYpBKEhx7b7rEsdw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.5.0", + "@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", + "@prisma/fetch-engine": "6.5.0", + "@prisma/get-platform": "6.5.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60.tgz", + "integrity": "sha512-iK3EmiVGFDCmXjSpdsKGNqy9hOdLnvYBrJB61far/oP03hlIxrb04OWmDjNTwtmZ3UZdA5MCvI+f+3k2jPTflQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.5.0.tgz", + "integrity": "sha512-3LhYA+FXP6pqY8FLHCjewyE8pGXXJ7BxZw2rhPq+CZAhvflVzq4K8Qly3OrmOkn6wGlz79nyLQdknyCG2HBTuA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.5.0", + "@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", + "@prisma/get-platform": "6.5.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.5.0.tgz", + "integrity": "sha512-xYcvyJwNMg2eDptBYFqFLUCfgi+wZLcj6HDMsj0Qw0irvauG4IKmkbywnqwok0B+k+W+p+jThM2DKTSmoPCkzw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.5.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.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" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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", + "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", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "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", + "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", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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", + "engines": { + "node": ">=18" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "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" + }, + "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" + }, + "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", + "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", + "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", + "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" + }, + "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", + "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", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "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", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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", + "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" + }, + "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", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.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", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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", + "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", + "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" + }, + "node_modules/prisma": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.5.0.tgz", + "integrity": "sha512-yUGXmWqv5F4PByMSNbYFxke/WbnyTLjnJ5bKr8fLkcnY7U5rU9rUTh/+Fja+gOrRxEgtCbCtca94IeITj4j/pg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.5.0", + "@prisma/engines": "6.5.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": 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" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "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" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "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", + "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", + "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", + "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", + "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" + }, + "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", + "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", + "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" + }, + "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", + "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" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "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", + "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", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.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", + "engines": { + "node": ">=12" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6468c4b --- /dev/null +++ b/package.json @@ -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 ", + "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" + } +} \ No newline at end of file diff --git a/api/prisma/schema.prisma b/prisma/schema.prisma similarity index 99% rename from api/prisma/schema.prisma rename to prisma/schema.prisma index 5496475..323cd8d 100644 --- a/api/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,4 +18,4 @@ model BankAccount { updatedAt DateTime @updatedAt @@map("bank_accounts") // Optional: specify table name in snake_case -} +} \ No newline at end of file diff --git a/api/src/server.ts b/src/server.ts similarity index 93% rename from api/src/server.ts rename to src/server.ts index 36e7795..8b62898 100644 --- a/api/src/server.ts +++ b/src/server.ts @@ -4,28 +4,18 @@ import Fastify, { FastifyInstance } from 'fastify'; import { Server, IncomingMessage, ServerResponse } from 'http'; import { PrismaClient, BankAccount } from '@prisma/client'; import dotenv from 'dotenv'; -import { z } from 'zod'; // Import Zod -import { ZodTypeProvider } from 'fastify-type-provider-zod'; // Import the type provider +import { z } from 'zod'; +import { ZodTypeProvider } from 'fastify-type-provider-zod'; -// Load environment variables dotenv.config(); const prisma = new PrismaClient(); -// --- Zod Schema Definitions --- - // Base schema for common fields, useful for reuse const bankAccountBaseSchema = z.object({ name: z.string().min(1, { message: "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" }), - /* - // 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) @@ -69,7 +59,8 @@ server.post( server.log.error(error); if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { 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); throw new Error('Failed to create bank account.'); diff --git a/tsconfig.json b/tsconfig.json index f0ea559..d10f74a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,9 @@ -// tsconfig.json { "compilerOptions": { "target": "ES2017", // Or newer "module": "CommonJS", "outDir": "./dist", - "rootDir": "api/src", + "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -13,6 +12,6 @@ "sourceMap": true, // Helpful for debugging "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 } \ No newline at end of file From 58e8dbc43c00328364056732f56eb83f66eb0ccf Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Mon, 7 Apr 2025 19:49:27 -0400 Subject: [PATCH 03/16] feat: update .gitignore and add reset environment script --- .gitignore | 4 ++-- reset-environment.sh | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100755 reset-environment.sh diff --git a/.gitignore b/.gitignore index 11ddd8d..a0d218e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules -# Keep environment variables out of version control -.env +dist +.env \ No newline at end of file diff --git a/reset-environment.sh b/reset-environment.sh new file mode 100755 index 0000000..bc814ce --- /dev/null +++ b/reset-environment.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -euo pipefail + +echo "Resetting finance development environment..." + +cd /home/acedanger/dev/finance || exit 1 + +npx prisma migrate reset --force || exit 1 + +docker compose down + +rm -f package-lock.json +[ -d dist ] && rm -rf dist || true +[ -d node_modules ] && rm -rf node_modules || true +npm install || exit 1 +npm run build || exit 1 + +docker compose -f docker-compose.yml up -d + +echo "$(date '+%Y-%m-%d %H:%M:%S') - Environment reset complete." From 06dfbf99653d523f72036f2bd890e1f5415a5fce Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Fri, 2 May 2025 23:51:18 -0400 Subject: [PATCH 04/16] feat: add initial implementation of Finance MCP server with REST API and setup instructions (ref #16) --- mcp/README.md | 141 ++++++++++++++++++++++++++ mcp/mcp-issues.md | 230 +++++++++++++++++++++++++++++++++++++++++++ mcp/mcp_server.py | 37 +++++++ mcp/requirements.txt | 3 + mcp/start_server.sh | 2 + 5 files changed, 413 insertions(+) create mode 100644 mcp/README.md create mode 100644 mcp/mcp-issues.md create mode 100644 mcp/mcp_server.py create mode 100644 mcp/requirements.txt create mode 100644 mcp/start_server.sh diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 0000000..9bc3b5d --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,141 @@ +# Finance MCP Server + +A Model Control Protocol (MCP) server implementation for financial transaction management. + +## Current Implementation + +The server currently provides a basic REST API for managing financial transactions with the following features: + +- FastAPI-based REST API +- In-memory transaction storage +- Basic CRUD operations for transactions +- Data validation using Pydantic models +- Auto-generated API documentation (Swagger UI) + +### Project Structure + +``` +mcp/ +├── mcp_server.py # Main server implementation +├── requirements.txt # Python dependencies +└── start_server.sh # Server startup script +``` + +## Setup Instructions + +1. Install Python dependencies: + ```bash + pip install -r requirements.txt + ``` + +2. Make the start script executable: + ```bash + chmod +x start_server.sh + ``` + +3. Start the server: + ```bash + ./start_server.sh + ``` + +The server will start on `http://localhost:8000` with the following endpoints: +- `/` - Health check endpoint +- `/docs` - Auto-generated API documentation (Swagger UI) +- `/transactions` - Transaction management endpoints + - GET /transactions - List all transactions + - POST /transactions - Create a new transaction + - GET /transactions/{id} - Get a specific transaction + +## API Usage + +### Create a Transaction +```bash +curl -X POST "http://localhost:8000/transactions" \ + -H "Content-Type: application/json" \ + -d '{ + "amount": 100.00, + "description": "Grocery shopping", + "category": "expenses" + }' +``` + +### List All Transactions +```bash +curl "http://localhost:8000/transactions" +``` + +## Recommended Next Steps + +1. **Database Integration** + - Implement PostgreSQL integration using SQLAlchemy + - Add database migrations + - Create proper data models + - Add transaction persistence + +2. **Authentication & Authorization** + - Implement JWT-based authentication + - Add user management + - Role-based access control + - API key authentication for machine-to-machine communication + +3. **Enhanced Features** + - Add transaction categories management + - Implement transaction search and filtering + - Add date range queries + - Support for different currencies + - Transaction metadata support + +4. **Security Enhancements** + - Input validation and sanitization + - Rate limiting + - CORS configuration + - Request logging and monitoring + - SSL/TLS configuration + +5. **Testing** + - Unit tests for models and endpoints + - Integration tests + - Load testing + - API documentation tests + +6. **Monitoring & Logging** + - Structured logging + - Prometheus metrics + - Health check endpoints + - Error tracking + - Performance monitoring + +7. **DevOps** + - Docker containerization + - CI/CD pipeline + - Environment configuration + - Backup strategy + - Deployment documentation + +8. **API Enhancements** + - Pagination + - Sorting options + - Bulk operations + - Webhook support + - Event streaming + +9. **Documentation** + - API documentation + - Development guide + - Deployment guide + - Contributing guidelines + +10. **Compliance & Standards** + - GDPR compliance + - Financial regulations compliance + - API versioning + - Error handling standards + - Audit logging + +## Contributing + +Please read our contributing guidelines before submitting pull requests. + +## License + +[Add your chosen license here] \ No newline at end of file diff --git a/mcp/mcp-issues.md b/mcp/mcp-issues.md new file mode 100644 index 0000000..dd8964d --- /dev/null +++ b/mcp/mcp-issues.md @@ -0,0 +1,230 @@ +# MCP Server Enhancement Issues + +## Priority 1: Core Infrastructure + +### Issue 1: Database Integration with Existing PostgreSQL Setup +**Labels**: `enhancement`, `database`, `priority-high` + +#### Description +Currently, the MCP server uses in-memory storage for transactions. We need to integrate it with the existing PostgreSQL database. + +#### Tasks +- [ ] Add SQLAlchemy to requirements.txt +- [ ] Create database models that align with existing Prisma schema +- [ ] Implement database connection handling +- [ ] Convert in-memory operations to database operations +- [ ] Add database migration system +- [ ] Add connection error handling +- [ ] Add database connection pooling + +#### Technical Details +- Use existing PostgreSQL setup from docker-compose.yml +- Align with existing Prisma schema structure +- Implement proper connection closing and resource cleanup + +#### Dependencies +- SQLAlchemy +- alembic (for migrations) +- psycopg2-binary + +**Estimated time**: 3-4 days + +--- + +### Issue 2: Authentication System Implementation +**Labels**: `enhancement`, `security`, `priority-high` + +#### Description +Implement secure authentication system for the MCP server endpoints. + +#### Tasks +- [ ] Add JWT authentication +- [ ] Create authentication middleware +- [ ] Implement user session management +- [ ] Add API key authentication for machine-to-machine communication +- [ ] Integrate with existing user system from the main application +- [ ] Add rate limiting for authentication attempts +- [ ] Implement secure password handling + +#### Technical Details +- Use Python-JWT for token handling +- Integrate with existing user schema from Prisma +- Implement token refresh mechanism +- Add proper error handling for auth failures + +#### Dependencies +- python-jose[cryptography] +- passlib[bcrypt] +- python-multipart + +#### Security Considerations +- Token expiration +- Secure password hashing +- Protection against brute force attacks + +**Estimated time**: 2-3 days + +--- + +## Priority 2: Feature Enhancements + +### Issue 3: Enhanced Transaction Features +**Labels**: `enhancement`, `feature`, `priority-medium` + +#### Description +Add enhanced features for transaction management and querying. + +#### Tasks +- [ ] Implement transaction categories management +- [ ] Add transaction search functionality + - [ ] Date range filtering + - [ ] Amount range filtering + - [ ] Category filtering + - [ ] Description search +- [ ] Add pagination for transaction listings +- [ ] Implement sorting options +- [ ] Add support for different currencies +- [ ] Implement transaction metadata support + +#### Technical Details +- Add proper indexing for search operations +- Implement efficient pagination +- Add currency conversion support +- Add proper validation for all new fields + +#### API Endpoints to Add +- GET /transactions/search +- GET /categories +- POST /categories +- PUT /transactions/{id}/metadata + +**Estimated time**: 3-4 days + +--- + +## Priority 3: Observability & Quality + +### Issue 4: Monitoring and Logging Setup +**Labels**: `enhancement`, `observability`, `priority-medium` + +#### Description +Implement monitoring and logging systems for better observability. + +#### Tasks +- [ ] Set up structured logging + - [ ] Request logging + - [ ] Error logging + - [ ] Performance metrics +- [ ] Add Prometheus metrics + - [ ] Request counts + - [ ] Response times + - [ ] Error rates +- [ ] Implement detailed health check endpoints +- [ ] Add performance monitoring +- [ ] Set up error tracking + +#### Technical Details +- Use Python logging module with JSON formatter +- Implement OpenTelemetry integration +- Add proper error context collection +- Implement custom metrics for financial operations + +#### Dependencies +- prometheus-client +- opentelemetry-api +- opentelemetry-sdk +- python-json-logger + +**Estimated time**: 2-3 days + +--- + +### Issue 5: Testing Infrastructure +**Labels**: `enhancement`, `testing`, `priority-medium` + +#### Description +Set up complete testing infrastructure for the MCP server. + +#### Tasks +- [ ] Set up unit testing framework +- [ ] Implement integration tests +- [ ] Add API documentation tests +- [ ] Create load testing suite +- [ ] Set up test data fixtures +- [ ] Implement CI pipeline for tests + +#### Test Coverage Areas +- [ ] Transaction operations +- [ ] Authentication +- [ ] Database operations +- [ ] Error handling +- [ ] API endpoints +- [ ] Data validation + +#### Technical Details +- Use pytest for testing +- Implement proper test database handling +- Add mock services for external dependencies +- Set up GitHub Actions for CI + +#### Dependencies +- pytest +- pytest-cov +- pytest-asyncio +- locust (for load testing) + +**Estimated time**: 4-5 days + +--- + +## Priority 4: DevOps & Documentation + +### Issue 6: Docker & Deployment Setup +**Labels**: `enhancement`, `devops`, `priority-low` + +#### Description +Containerize the MCP server and set up deployment infrastructure. + +#### Tasks +- [ ] Create Dockerfile for MCP server +- [ ] Update docker-compose.yml to include MCP service +- [ ] Set up environment configuration +- [ ] Create deployment scripts +- [ ] Implement backup strategy +- [ ] Add health checks for container orchestration + +#### Technical Details +- Multi-stage Docker build +- Environment variable configuration +- Volume management for persistent data +- Container health monitoring + +**Estimated time**: 2-3 days + +--- + +### Issue 7: Documentation Enhancement +**Labels**: `documentation`, `priority-low` + +#### Description +Create comprehensive documentation for the MCP server. + +#### Tasks +- [ ] Create API documentation +- [ ] Write development guide +- [ ] Create deployment guide +- [ ] Add contributing guidelines +- [ ] Document security practices +- [ ] Add troubleshooting guide + +#### Areas to Cover +- Setup instructions +- API endpoints and usage +- Authentication flows +- Database schema +- Configuration options +- Deployment procedures +- Contributing workflow +- Security best practices + +**Estimated time**: 2-3 days \ No newline at end of file diff --git a/mcp/mcp_server.py b/mcp/mcp_server.py new file mode 100644 index 0000000..63ca23f --- /dev/null +++ b/mcp/mcp_server.py @@ -0,0 +1,37 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from datetime import datetime +from typing import List, Optional + +app = FastAPI(title="Finance MCP Server") + +class Transaction(BaseModel): + id: Optional[int] + amount: float + description: str + timestamp: datetime = datetime.now() + category: str + +# In-memory storage (replace with database in production) +transactions: List[Transaction] = [] + +@app.get("/") +async def root(): + return {"status": "running", "service": "Finance MCP Server"} + +@app.get("/transactions") +async def get_transactions(): + return transactions + +@app.post("/transactions") +async def create_transaction(transaction: Transaction): + transaction.id = len(transactions) + 1 + transactions.append(transaction) + return transaction + +@app.get("/transactions/{transaction_id}") +async def get_transaction(transaction_id: int): + for tx in transactions: + if tx.id == transaction_id: + return tx + raise HTTPException(status_code=404, detail="Transaction not found") \ No newline at end of file diff --git a/mcp/requirements.txt b/mcp/requirements.txt new file mode 100644 index 0000000..34d56f8 --- /dev/null +++ b/mcp/requirements.txt @@ -0,0 +1,3 @@ +fastapi==0.68.0 +uvicorn==0.15.0 +pydantic==1.8.2 \ No newline at end of file diff --git a/mcp/start_server.sh b/mcp/start_server.sh new file mode 100644 index 0000000..394c27e --- /dev/null +++ b/mcp/start_server.sh @@ -0,0 +1,2 @@ +#!/bin/bash +uvicorn mcp_server:app --reload --port 8000 \ No newline at end of file From 79e38310045b666b25a88987dfc6b9dcc570638e Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 3 May 2025 08:18:41 -0400 Subject: [PATCH 05/16] docs: add application goal and project setup instructions to documentation --- .github/APP_GOAL.md | 85 +++++++++++++++++++++++++++++++++++++++++++++ overview.md | 8 +++-- 2 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 .github/APP_GOAL.md diff --git a/.github/APP_GOAL.md b/.github/APP_GOAL.md new file mode 100644 index 0000000..bc5a03e --- /dev/null +++ b/.github/APP_GOAL.md @@ -0,0 +1,85 @@ +# Application Goal + +From a high level perspective, I want to build a web application that allows users to manage their financial transactions and institutions. The application should provide a user-friendly interface for users to create, update, delete, and view their financial transactions and institutions. It should also allow users to group their transactions by date or institution for better organization and analysis. +The application should be built using modern web technologies and should be self-hosted. It should also include authentication and authorization features to ensure that users can securely access their data. The application should be designed with scalability in mind, allowing for future enhancements and additional features. +The application should be easy to maintain and extend, with a focus on code quality and best practices. It should also include automated testing and continuous integration/continuous deployment (CI/CD) processes to ensure that changes can be made safely and efficiently. +The application should be well-documented, with clear instructions for installation, configuration, and usage. The documentation should also include information on how to contribute to the project and report issues. +The application should be designed to be user-friendly and accessible, with a focus on providing a positive user experience. It should also include features for data visualization and reporting, allowing users to gain insights into their financial transactions and institutions. +The application should be designed to be responsive and work well on a variety of devices, including desktops, tablets, and smartphones. It should also include features for data import and export, allowing users to easily transfer their data to and from other applications. +The application should be designed to be secure, with a focus on protecting user data and preventing unauthorized access. It should also include features for data backup and recovery, ensuring that users can recover their data in case of loss or corruption. +The application should be designed to be modular and extensible, allowing for the addition of new features and functionality in the future. It should also include features for user feedback and support, allowing users to report issues and request new features. +The application should be designed to be performant, with a focus on minimizing load times and optimizing resource usage. It should also include features for monitoring and logging, allowing developers to track performance and identify issues. + +--- + +## Features + +- User authentication and authorization using OAuth2 +- User profile management +- Financial institution management (create, update, delete, view) +- Financial transaction management (create, update, delete, view) +- Grouping transactions by financial institution +- Grouping transactions by date +- Responsive design for desktop and mobile devices +- Data visualization and reporting features +- Data import and export features +- Data backup and recovery features +- Modular and extensible architecture +- User feedback and support features +- Monitoring and logging features +- Automated testing and CI/CD processes +- Well-documented codebase and user documentation +- Clear instructions for installation, configuration, and usage +- Code quality and best practices +- User-friendly and accessible design +- Performance optimization and resource usage minimization +- Support for recurring transactions and budgeting +- Allow users to set up recurring transactions for regular expenses or income +- Budgeting features to help users track their spending and savings goals + +--- + +## Technical Requirements + +I want to use the following technologies: + +- **Frontend**: React, TypeScript, Tailwind CSS, Zod, Cors +- **Backend**: Node.js, Fastify +- **Database**: PostgreSQL, Prisma +- **Deployment**: Docker, Self-hosted +- **Authentication**: OAuth2 +- **Testing**: Jest, React Testing Library +- **CI/CD**: GitHub Actions +- **Documentation**: Docusaurus + +--- + +## User Stories + +- As a user, I want to be able to create, update, delete, and view - financial institutions. +- As a user, I want to be able to view a list of financial institutions in - the system. +- As a user, I want to be able to create, update, delete, and view - financial transactions. +- As a user, I want to be able to view a list of financial transactions in - the system. +- As a user, I want to be able to view a list of financial transactions - grouped by financial institution. +- As a user, I want to be able to view a list of financial transactions - grouped by date. +- As a user, I want to be able log in using oauth2. +- As a user, I want to be able to log out. +- As a user, I want to be able to view my profile. +- As a user, I want to be able to update my profile. + +--- + +## Out of Scope + +--- + +## Open Questions + +- What specific data visualization and reporting features do you want to include? +- What specific data import and export features do you want to include? +- What specific data backup and recovery features do you want to include? +- What specific user feedback and support features do you want to include? +- What specific monitoring and logging features do you want to include? +- What specific performance optimization and resource usage minimization techniques do you want to include? +- What specific security features do you want to include? +- What specific user experience and accessibility features do you want to include? diff --git a/overview.md b/overview.md index 0af4a20..13da0c3 100644 --- a/overview.md +++ b/overview.md @@ -9,14 +9,16 @@ Okay, let's set up a skeleton Fastify API project with Prisma to interact with a **Project Setup Steps:** -1. **Create Project Directory & Initialize:** +1. **Create Project Directory & Initialize:** + ```bash mkdir finance-api cd finance-api npm init -y ``` -2. **Install Dependencies:** +2. **Install Dependencies:** + ```bash # Runtime dependencies npm install fastify @prisma/client dotenv @@ -408,4 +410,4 @@ Okay, let's set up a skeleton Fastify API project with Prisma to interact with a 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. \ No newline at end of file +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. From d5babec1879c45d4199ef98981139aa779e11dd8 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 3 May 2025 10:51:11 -0400 Subject: [PATCH 06/16] Configure GitHub MCP server in devcontainer environment (Closes #25) --- .devcontainer/.env.example | 13 +++++++++++++ .devcontainer/devcontainer.json | 19 ++++++++++++++++--- .gitignore | 3 +++ README.md | 31 ++++++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 .devcontainer/.env.example diff --git a/.devcontainer/.env.example b/.devcontainer/.env.example new file mode 100644 index 0000000..85e5df8 --- /dev/null +++ b/.devcontainer/.env.example @@ -0,0 +1,13 @@ +# GitHub MCP Server Configuration +GITHUB_PERSONAL_ACCESS_TOKEN=your_token_here + +# PostgreSQL Configuration +POSTGRES_USER=financeuser +POSTGRES_PASSWORD=changeme +POSTGRES_DB=finance +POSTGRES_PORT=5432 + +# pgAdmin Configuration +PGADMIN_DEFAULT_EMAIL=peter@peterwood.dev +PGADMIN_DEFAULT_PASSWORD=admin +PGADMIN_PORT=5050 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 825c526..82aac0e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,9 @@ "name": "Finance App Development", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", "features": { - "ghcr.io/devcontainers/features/git:1": {} + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} }, "customizations": { "vscode": { @@ -40,12 +42,23 @@ "**/node_modules": true, "**/.idea": true }, - "terminal.integrated.defaultProfile.linux": "bash" + "terminal.integrated.defaultProfile.linux": "bash", + "mcp.servers.github": { + "command": "docker", + "args": ["run", "-i", "--rm", "--env-file", "${containerWorkspaceFolder}/.devcontainer/.env", "ghcr.io/github/github-mcp-server"], + "env": {} + } } } }, "forwardPorts": [3000], "postCreateCommand": "npm install && npm run check", "remoteUser": "node", - "mounts": ["type=bind,source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,readonly"] + "mounts": [ + "type=bind,source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,readonly" + ], + "remoteEnv": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${localEnv:GITHUB_PERSONAL_ACCESS_TOKEN}" + }, + "postStartCommand": "gh auth status || gh auth login" } diff --git a/.gitignore b/.gitignore index d46f4b7..2a60110 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ pnpm-debug.log* # Test coverage coverage/ + +# DevContainer environment files +.devcontainer/.env diff --git a/README.md b/README.md index d18d704..5509c4c 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,33 @@ A web application for managing financial transactions across multiple bank accou * **Key Features (Implemented & Planned):** Account switching, transaction listing, adding, editing, and deleting transactions. ## Logs -This app is currently deployed using Cloudflare Pages. The logs can be viewed with the `npx wrangler pages deployment tail --project-name finance` command. T \ No newline at end of file +This app is currently deployed using Cloudflare Pages. The logs can be viewed with the `npx wrangler pages deployment tail --project-name finance` command. + +## Development Environment Setup + +### Prerequisites +- VS Code with Remote Containers extension +- Docker and Docker Compose +- Git + +### Initial Setup +1. Clone the repository +2. Copy `.devcontainer/.env.example` to `.devcontainer/.env` +3. Update the environment variables in `.devcontainer/.env` +4. Open the project in VS Code and select "Reopen in Container" when prompted + +### GitHub MCP Server +The project uses GitHub's MCP server for development tasks. The server runs in a Docker container and is automatically configured when you open the project in a devcontainer. + +#### Configuration +- The MCP server uses GitHub authentication via Personal Access Token +- Token is stored securely in `.devcontainer/.env` (not committed to repository) +- GitHub CLI is installed in the devcontainer for easier authentication management +- Container health monitoring is configured + +#### Usage +The MCP server will automatically start when you open the project in a devcontainer. If you need to manually authenticate: +1. Run `gh auth login` in the terminal +2. Follow the prompts to authenticate with your GitHub account + +### Database Setup \ No newline at end of file From f68957ef053ef9d31ccb777b2b8b328658a6b7a1 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 3 May 2025 20:46:05 -0400 Subject: [PATCH 07/16] removed MCP server code and moved to acedanger/finance-mcp --- mcp/README.md | 141 -------------------------- mcp/mcp-issues.md | 230 ------------------------------------------- mcp/mcp_server.py | 37 ------- mcp/requirements.txt | 3 - mcp/start_server.sh | 2 - 5 files changed, 413 deletions(-) delete mode 100644 mcp/README.md delete mode 100644 mcp/mcp-issues.md delete mode 100644 mcp/mcp_server.py delete mode 100644 mcp/requirements.txt delete mode 100644 mcp/start_server.sh diff --git a/mcp/README.md b/mcp/README.md deleted file mode 100644 index 9bc3b5d..0000000 --- a/mcp/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# Finance MCP Server - -A Model Control Protocol (MCP) server implementation for financial transaction management. - -## Current Implementation - -The server currently provides a basic REST API for managing financial transactions with the following features: - -- FastAPI-based REST API -- In-memory transaction storage -- Basic CRUD operations for transactions -- Data validation using Pydantic models -- Auto-generated API documentation (Swagger UI) - -### Project Structure - -``` -mcp/ -├── mcp_server.py # Main server implementation -├── requirements.txt # Python dependencies -└── start_server.sh # Server startup script -``` - -## Setup Instructions - -1. Install Python dependencies: - ```bash - pip install -r requirements.txt - ``` - -2. Make the start script executable: - ```bash - chmod +x start_server.sh - ``` - -3. Start the server: - ```bash - ./start_server.sh - ``` - -The server will start on `http://localhost:8000` with the following endpoints: -- `/` - Health check endpoint -- `/docs` - Auto-generated API documentation (Swagger UI) -- `/transactions` - Transaction management endpoints - - GET /transactions - List all transactions - - POST /transactions - Create a new transaction - - GET /transactions/{id} - Get a specific transaction - -## API Usage - -### Create a Transaction -```bash -curl -X POST "http://localhost:8000/transactions" \ - -H "Content-Type: application/json" \ - -d '{ - "amount": 100.00, - "description": "Grocery shopping", - "category": "expenses" - }' -``` - -### List All Transactions -```bash -curl "http://localhost:8000/transactions" -``` - -## Recommended Next Steps - -1. **Database Integration** - - Implement PostgreSQL integration using SQLAlchemy - - Add database migrations - - Create proper data models - - Add transaction persistence - -2. **Authentication & Authorization** - - Implement JWT-based authentication - - Add user management - - Role-based access control - - API key authentication for machine-to-machine communication - -3. **Enhanced Features** - - Add transaction categories management - - Implement transaction search and filtering - - Add date range queries - - Support for different currencies - - Transaction metadata support - -4. **Security Enhancements** - - Input validation and sanitization - - Rate limiting - - CORS configuration - - Request logging and monitoring - - SSL/TLS configuration - -5. **Testing** - - Unit tests for models and endpoints - - Integration tests - - Load testing - - API documentation tests - -6. **Monitoring & Logging** - - Structured logging - - Prometheus metrics - - Health check endpoints - - Error tracking - - Performance monitoring - -7. **DevOps** - - Docker containerization - - CI/CD pipeline - - Environment configuration - - Backup strategy - - Deployment documentation - -8. **API Enhancements** - - Pagination - - Sorting options - - Bulk operations - - Webhook support - - Event streaming - -9. **Documentation** - - API documentation - - Development guide - - Deployment guide - - Contributing guidelines - -10. **Compliance & Standards** - - GDPR compliance - - Financial regulations compliance - - API versioning - - Error handling standards - - Audit logging - -## Contributing - -Please read our contributing guidelines before submitting pull requests. - -## License - -[Add your chosen license here] \ No newline at end of file diff --git a/mcp/mcp-issues.md b/mcp/mcp-issues.md deleted file mode 100644 index dd8964d..0000000 --- a/mcp/mcp-issues.md +++ /dev/null @@ -1,230 +0,0 @@ -# MCP Server Enhancement Issues - -## Priority 1: Core Infrastructure - -### Issue 1: Database Integration with Existing PostgreSQL Setup -**Labels**: `enhancement`, `database`, `priority-high` - -#### Description -Currently, the MCP server uses in-memory storage for transactions. We need to integrate it with the existing PostgreSQL database. - -#### Tasks -- [ ] Add SQLAlchemy to requirements.txt -- [ ] Create database models that align with existing Prisma schema -- [ ] Implement database connection handling -- [ ] Convert in-memory operations to database operations -- [ ] Add database migration system -- [ ] Add connection error handling -- [ ] Add database connection pooling - -#### Technical Details -- Use existing PostgreSQL setup from docker-compose.yml -- Align with existing Prisma schema structure -- Implement proper connection closing and resource cleanup - -#### Dependencies -- SQLAlchemy -- alembic (for migrations) -- psycopg2-binary - -**Estimated time**: 3-4 days - ---- - -### Issue 2: Authentication System Implementation -**Labels**: `enhancement`, `security`, `priority-high` - -#### Description -Implement secure authentication system for the MCP server endpoints. - -#### Tasks -- [ ] Add JWT authentication -- [ ] Create authentication middleware -- [ ] Implement user session management -- [ ] Add API key authentication for machine-to-machine communication -- [ ] Integrate with existing user system from the main application -- [ ] Add rate limiting for authentication attempts -- [ ] Implement secure password handling - -#### Technical Details -- Use Python-JWT for token handling -- Integrate with existing user schema from Prisma -- Implement token refresh mechanism -- Add proper error handling for auth failures - -#### Dependencies -- python-jose[cryptography] -- passlib[bcrypt] -- python-multipart - -#### Security Considerations -- Token expiration -- Secure password hashing -- Protection against brute force attacks - -**Estimated time**: 2-3 days - ---- - -## Priority 2: Feature Enhancements - -### Issue 3: Enhanced Transaction Features -**Labels**: `enhancement`, `feature`, `priority-medium` - -#### Description -Add enhanced features for transaction management and querying. - -#### Tasks -- [ ] Implement transaction categories management -- [ ] Add transaction search functionality - - [ ] Date range filtering - - [ ] Amount range filtering - - [ ] Category filtering - - [ ] Description search -- [ ] Add pagination for transaction listings -- [ ] Implement sorting options -- [ ] Add support for different currencies -- [ ] Implement transaction metadata support - -#### Technical Details -- Add proper indexing for search operations -- Implement efficient pagination -- Add currency conversion support -- Add proper validation for all new fields - -#### API Endpoints to Add -- GET /transactions/search -- GET /categories -- POST /categories -- PUT /transactions/{id}/metadata - -**Estimated time**: 3-4 days - ---- - -## Priority 3: Observability & Quality - -### Issue 4: Monitoring and Logging Setup -**Labels**: `enhancement`, `observability`, `priority-medium` - -#### Description -Implement monitoring and logging systems for better observability. - -#### Tasks -- [ ] Set up structured logging - - [ ] Request logging - - [ ] Error logging - - [ ] Performance metrics -- [ ] Add Prometheus metrics - - [ ] Request counts - - [ ] Response times - - [ ] Error rates -- [ ] Implement detailed health check endpoints -- [ ] Add performance monitoring -- [ ] Set up error tracking - -#### Technical Details -- Use Python logging module with JSON formatter -- Implement OpenTelemetry integration -- Add proper error context collection -- Implement custom metrics for financial operations - -#### Dependencies -- prometheus-client -- opentelemetry-api -- opentelemetry-sdk -- python-json-logger - -**Estimated time**: 2-3 days - ---- - -### Issue 5: Testing Infrastructure -**Labels**: `enhancement`, `testing`, `priority-medium` - -#### Description -Set up complete testing infrastructure for the MCP server. - -#### Tasks -- [ ] Set up unit testing framework -- [ ] Implement integration tests -- [ ] Add API documentation tests -- [ ] Create load testing suite -- [ ] Set up test data fixtures -- [ ] Implement CI pipeline for tests - -#### Test Coverage Areas -- [ ] Transaction operations -- [ ] Authentication -- [ ] Database operations -- [ ] Error handling -- [ ] API endpoints -- [ ] Data validation - -#### Technical Details -- Use pytest for testing -- Implement proper test database handling -- Add mock services for external dependencies -- Set up GitHub Actions for CI - -#### Dependencies -- pytest -- pytest-cov -- pytest-asyncio -- locust (for load testing) - -**Estimated time**: 4-5 days - ---- - -## Priority 4: DevOps & Documentation - -### Issue 6: Docker & Deployment Setup -**Labels**: `enhancement`, `devops`, `priority-low` - -#### Description -Containerize the MCP server and set up deployment infrastructure. - -#### Tasks -- [ ] Create Dockerfile for MCP server -- [ ] Update docker-compose.yml to include MCP service -- [ ] Set up environment configuration -- [ ] Create deployment scripts -- [ ] Implement backup strategy -- [ ] Add health checks for container orchestration - -#### Technical Details -- Multi-stage Docker build -- Environment variable configuration -- Volume management for persistent data -- Container health monitoring - -**Estimated time**: 2-3 days - ---- - -### Issue 7: Documentation Enhancement -**Labels**: `documentation`, `priority-low` - -#### Description -Create comprehensive documentation for the MCP server. - -#### Tasks -- [ ] Create API documentation -- [ ] Write development guide -- [ ] Create deployment guide -- [ ] Add contributing guidelines -- [ ] Document security practices -- [ ] Add troubleshooting guide - -#### Areas to Cover -- Setup instructions -- API endpoints and usage -- Authentication flows -- Database schema -- Configuration options -- Deployment procedures -- Contributing workflow -- Security best practices - -**Estimated time**: 2-3 days \ No newline at end of file diff --git a/mcp/mcp_server.py b/mcp/mcp_server.py deleted file mode 100644 index 63ca23f..0000000 --- a/mcp/mcp_server.py +++ /dev/null @@ -1,37 +0,0 @@ -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel -from datetime import datetime -from typing import List, Optional - -app = FastAPI(title="Finance MCP Server") - -class Transaction(BaseModel): - id: Optional[int] - amount: float - description: str - timestamp: datetime = datetime.now() - category: str - -# In-memory storage (replace with database in production) -transactions: List[Transaction] = [] - -@app.get("/") -async def root(): - return {"status": "running", "service": "Finance MCP Server"} - -@app.get("/transactions") -async def get_transactions(): - return transactions - -@app.post("/transactions") -async def create_transaction(transaction: Transaction): - transaction.id = len(transactions) + 1 - transactions.append(transaction) - return transaction - -@app.get("/transactions/{transaction_id}") -async def get_transaction(transaction_id: int): - for tx in transactions: - if tx.id == transaction_id: - return tx - raise HTTPException(status_code=404, detail="Transaction not found") \ No newline at end of file diff --git a/mcp/requirements.txt b/mcp/requirements.txt deleted file mode 100644 index 34d56f8..0000000 --- a/mcp/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -fastapi==0.68.0 -uvicorn==0.15.0 -pydantic==1.8.2 \ No newline at end of file diff --git a/mcp/start_server.sh b/mcp/start_server.sh deleted file mode 100644 index 394c27e..0000000 --- a/mcp/start_server.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -uvicorn mcp_server:app --reload --port 8000 \ No newline at end of file From 377cc6346586b870ed16c4783938d54bc0ceae32 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sat, 3 May 2025 21:06:27 -0400 Subject: [PATCH 08/16] chore: updated libraries used; changed dns servers used to mitigate apt update errors --- .devcontainer/devcontainer.json | 3 + package-lock.json | 679 ++++++++++++++++---------------- 2 files changed, 351 insertions(+), 331 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 82aac0e..8394ddb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,11 +1,14 @@ { "name": "Finance App Development", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", + "workspaceFolder": "/home/acedanger/dev/finance", + "workspaceMount": "source=${localWorkspaceFolder},target=/home/acedanger/dev/finance,type=bind,consistency=cached", "features": { "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, + "runArgs": ["--dns", "8.8.8.8", "--dns", "8.8.4.4"], "customizations": { "vscode": { "extensions": [ diff --git a/package-lock.json b/package-lock.json index affb792..6121570 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,16 +42,16 @@ } }, "node_modules/@astrojs/cloudflare": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@astrojs/cloudflare/-/cloudflare-12.5.1.tgz", - "integrity": "sha512-J7LpDw7G/u/7/+XLx/zNFRwSJxPb+CimFSdJrQsHEXSzheJnqL8lExfiG/P94aJyEMa1aw5R5ECFqnEjdbbjRw==", + "version": "12.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/cloudflare/-/cloudflare-12.5.2.tgz", + "integrity": "sha512-zhqBJmy0zXqpniWuAbpWY6CCNdkq3Olm2LMHv0lryvY6CUFcZbDzTK225zgz//r5zPj8ty1qbvWRnXgCaiaaYQ==", "license": "MIT", "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/underscore-redirects": "0.6.1", "@cloudflare/workers-types": "^4.20250327.0", "tinyglobby": "^0.2.12", - "vite": "^6.2.6", + "vite": "^6.3.4", "wrangler": "^4.5.1" }, "peerDependencies": { @@ -59,9 +59,9 @@ } }, "node_modules/@astrojs/compiler": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.11.0.tgz", - "integrity": "sha512-zZOO7i+JhojO8qmlyR/URui6LyfHJY6m+L9nwyX5GiKD78YoRaZ5tzz6X0fkl+5bD3uwlDHayf6Oe8Fu36RKNg==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.12.0.tgz", + "integrity": "sha512-7bCjW6tVDpUurQLeKBUN9tZ5kSv5qYrGmcn0sG0IwacL7isR2ZbyyA3AdZ4uxsuUFOS2SlgReTH7wkxO6zpqWA==", "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { @@ -126,14 +126,14 @@ } }, "node_modules/@astrojs/react": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.2.5.tgz", - "integrity": "sha512-NIBB51z4OP57gYmDQCu2UpQLm61kzdahELud/X8TGcfvcVoMvp5m4810TrRQ3V3vebTGHRGYICoOFTS33nuT+g==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.2.7.tgz", + "integrity": "sha512-/wM90noT/6QyJEOGdDmDbq2D9qZooKTJNG1M8olmsW5ns6bJ7uxG5fzkYxcpA3WUTD6Dj6NtpEqchvb5h8Fa+g==", "license": "MIT", "dependencies": { - "@vitejs/plugin-react": "^4.3.4", - "ultrahtml": "^1.5.3", - "vite": "^6.2.6" + "@vitejs/plugin-react": "^4.4.1", + "ultrahtml": "^1.6.0", + "vite": "^6.3.4" }, "engines": { "node": "^18.17.1 || ^20.3.0 || >=22.0.0" @@ -170,44 +170,44 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz", + "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -222,23 +222,14 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -248,13 +239,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz", + "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -263,46 +254,28 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -312,61 +285,61 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", + "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -376,12 +349,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", - "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -391,12 +364,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", - "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -406,30 +379,30 @@ } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", + "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -438,13 +411,13 @@ } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -675,9 +648,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250424.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250424.0.tgz", - "integrity": "sha512-E+9tyQfwKwg7iz+vI50UeF9m9MhO6uCTnn6VPBTobhgi0rKcfmCteUGz6YJejG6ex9OIfFHg/tIcr1+ywGZtiA==", + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250428.0.tgz", + "integrity": "sha512-6nVe9oV4Hdec6ctzMtW80TiDvNTd2oFPi3VsKqSDVaJSJbL+4b6seyJ7G/UEPI+si6JhHBSLV2/9lNXNGLjClA==", "cpu": [ "x64" ], @@ -691,9 +664,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250424.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250424.0.tgz", - "integrity": "sha512-5vReSs+Gx4vPNR3zoU3a7BVBoTEc7aoe2gGcaxSSQKMOvVkp3bo9poOGZbISodhYnCCRXltZcl8Vgyi0l/YZLA==", + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250428.0.tgz", + "integrity": "sha512-/TB7bh7SIJ5f+6r4PHsAz7+9Qal/TK1cJuKFkUno1kqGlZbdrMwH0ATYwlWC/nBFeu2FB3NUolsTntEuy23hnQ==", "cpu": [ "arm64" ], @@ -707,9 +680,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250424.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250424.0.tgz", - "integrity": "sha512-8kBNy7LpW/E4XKGrx/1Xql3Hfy8viDb+tFudu+sN/b6A2tNczNoOzDyNeWeWa99/zfyzncah1l0Wl2RBmVvY+Q==", + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250428.0.tgz", + "integrity": "sha512-9eCbj+R3CKqpiXP6DfAA20DxKge+OTj7Hyw3ZewiEhWH9INIHiJwJQYybu4iq9kJEGjnGvxgguLFjSCWm26hgg==", "cpu": [ "x64" ], @@ -723,9 +696,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250424.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250424.0.tgz", - "integrity": "sha512-R4wLZNobQo5K96e3BEaTwCbZhyspeoW81k/yrkSRseLpSoIpLNguw6ckk5sGCjUkXEZQyu9TG6PzdYqlQo70gw==", + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250428.0.tgz", + "integrity": "sha512-D9NRBnW46nl1EQsP13qfkYb5lbt4C6nxl38SBKY/NOcZAUoHzNB5K0GaK8LxvpkM7X/97ySojlMfR5jh5DNXYQ==", "cpu": [ "arm64" ], @@ -739,9 +712,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250424.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250424.0.tgz", - "integrity": "sha512-uwzZhNaKjJKq6NGFPd0hQWecpf5OTZCrlWKQZm4kkufZ7uIzkn5t3kOjh/J3L9puM/GvIPxCiDUE2aG66P6YxA==", + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250428.0.tgz", + "integrity": "sha512-RQCRj28eitjKD0tmei6iFOuWqMuHMHdNGEigRmbkmuTlpbWHNAoHikgCzZQ/dkKDdatA76TmcpbyECNf31oaTA==", "cpu": [ "x64" ], @@ -755,9 +728,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20250424.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250424.0.tgz", - "integrity": "sha512-tolHPBVlYSIZq5GWlGbbSqXg1P79u059YJ19cFULwRCF/KpElb9YDq/D9oPxqpw/niS9AvzVBCR5RCxsWv4LDQ==", + "version": "4.20250503.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250503.0.tgz", + "integrity": "sha512-+vSa683Z5sC29NLF/o6GJgxRvIEAtwPobpwPyvMpJTY8QvLawEO0A7BVa6+xjDWSAWQcefyEXycS8Ici7d/FZg==", "license": "MIT OR Apache-2.0" }, "node_modules/@cspotcode/source-map-support": { @@ -1769,9 +1742,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", + "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", "cpu": [ "arm" ], @@ -1782,9 +1755,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", + "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", "cpu": [ "arm64" ], @@ -1795,9 +1768,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", + "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", "cpu": [ "arm64" ], @@ -1808,9 +1781,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", + "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", "cpu": [ "x64" ], @@ -1821,9 +1794,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", + "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", "cpu": [ "arm64" ], @@ -1834,9 +1807,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", + "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", "cpu": [ "x64" ], @@ -1847,9 +1820,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", + "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", "cpu": [ "arm" ], @@ -1860,9 +1833,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", + "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", "cpu": [ "arm" ], @@ -1873,9 +1846,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", + "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", "cpu": [ "arm64" ], @@ -1886,9 +1859,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", + "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", "cpu": [ "arm64" ], @@ -1899,9 +1872,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", + "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", "cpu": [ "loong64" ], @@ -1912,9 +1885,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", + "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", "cpu": [ "ppc64" ], @@ -1925,9 +1898,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", + "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", "cpu": [ "riscv64" ], @@ -1938,9 +1911,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", + "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", "cpu": [ "riscv64" ], @@ -1951,9 +1924,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", + "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", "cpu": [ "s390x" ], @@ -1964,9 +1937,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", + "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", "cpu": [ "x64" ], @@ -1977,9 +1950,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", + "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", "cpu": [ "x64" ], @@ -1990,9 +1963,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", + "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", "cpu": [ "arm64" ], @@ -2003,9 +1976,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", + "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", "cpu": [ "ia32" ], @@ -2016,9 +1989,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", + "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", "cpu": [ "x64" ], @@ -2208,9 +2181,9 @@ } }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2227,9 +2200,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.1.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", - "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "version": "19.1.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz", + "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -2448,6 +2421,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -2599,9 +2581,9 @@ } }, "node_modules/astro": { - "version": "5.7.5", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.7.5.tgz", - "integrity": "sha512-c59YuYiXyWWnUMOBlDczrjqKzF0dJQP20EP9vqDggcyKm//tEt9iJHNwoYq4r3UeO9UJCwlGK8FwaGTAwwF3IA==", + "version": "5.7.10", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.7.10.tgz", + "integrity": "sha512-9TQcFZqP2w6//JXXUHfw8/5PX7KUx9EkG5O3m+hISuyeUztvjY1q5+p7+C5HiXyg24Zs3KkpieoL5BGRXGCAGA==", "license": "MIT", "dependencies": { "@astrojs/compiler": "^2.11.0", @@ -2651,11 +2633,11 @@ "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", - "unifont": "~0.2.0", + "unifont": "~0.4.1", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", - "vite": "^6.2.6", + "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", @@ -2680,6 +2662,18 @@ "sharp": "^0.33.3" } }, + "node_modules/astro/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", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2807,9 +2801,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "funding": [ { "type": "opencollective", @@ -2826,10 +2820,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -2892,9 +2886,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001715", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", - "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "version": "1.0.30001716", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz", + "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==", "funding": [ { "type": "opencollective", @@ -3423,9 +3417,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.142", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.142.tgz", - "integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w==", + "version": "1.5.149", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.149.tgz", + "integrity": "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -3434,12 +3428,6 @@ "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "license": "MIT" }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3904,19 +3892,19 @@ } }, "node_modules/h3": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.1.tgz", - "integrity": "sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.3.tgz", + "integrity": "sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ==", "license": "MIT", "dependencies": { "cookie-es": "^1.2.2", - "crossws": "^0.3.3", + "crossws": "^0.3.4", "defu": "^6.1.4", - "destr": "^2.0.3", + "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.0", "radix3": "^1.1.2", - "ufo": "^1.5.4", + "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, @@ -4451,10 +4439,13 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, "node_modules/magic-string": { "version": "0.30.17", @@ -4492,6 +4483,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -5353,9 +5357,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250424.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250424.0.tgz", - "integrity": "sha512-eROPDAx4KCIFtfRyRV9d3FJaF94UjW57gRUZz7gk2wyyOuHYVVJnWWyWrZfvRqzd4WfoJLDZlczhlZ9aZgJ2cQ==", + "version": "4.20250428.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250428.1.tgz", + "integrity": "sha512-M3qcJXjeAEimHrEeWXEhrJiC3YHB5M3QSqqK67pOTI+lHn0QyVG/2iFUjVJ/nv+i10uxeAEva8GRGeu+tKRCmQ==", "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -5365,7 +5369,7 @@ "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", - "workerd": "1.20250424.0", + "workerd": "1.20250428.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" @@ -5389,15 +5393,6 @@ "node": ">=0.4.0" } }, - "node_modules/miniflare/node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/miniflare/node_modules/zod": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", @@ -5612,19 +5607,18 @@ } }, "node_modules/oniguruma-parser": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.11.2.tgz", - "integrity": "sha512-F7Ld4oDZJCI5/wCZ8AOffQbqjSzIRpKH7I/iuSs1SkhZeCj0wS6PMZ4W6VA16TWHrAo0Y9bBKEJOe7tvwcTXnw==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.2.0.tgz", - "integrity": "sha512-MDPs6KSOLS0tKQ7joqg44dRIRZUyotfTy0r+7oEEs6VwWWP0+E2PPDYWMFN0aqOjRyWHBYq7RfKw9GQk2S2z5g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", "license": "MIT", "dependencies": { - "emoji-regex-xs": "^1.0.0", - "oniguruma-parser": "^0.11.0", + "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } @@ -5748,6 +5742,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", @@ -6171,9 +6172,9 @@ } }, "node_modules/rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", + "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", "license": "MIT", "dependencies": { "@types/estree": "1.0.7" @@ -6186,26 +6187,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@rollup/rollup-android-arm-eabi": "4.40.1", + "@rollup/rollup-android-arm64": "4.40.1", + "@rollup/rollup-darwin-arm64": "4.40.1", + "@rollup/rollup-darwin-x64": "4.40.1", + "@rollup/rollup-freebsd-arm64": "4.40.1", + "@rollup/rollup-freebsd-x64": "4.40.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", + "@rollup/rollup-linux-arm-musleabihf": "4.40.1", + "@rollup/rollup-linux-arm64-gnu": "4.40.1", + "@rollup/rollup-linux-arm64-musl": "4.40.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-musl": "4.40.1", + "@rollup/rollup-linux-s390x-gnu": "4.40.1", + "@rollup/rollup-linux-x64-gnu": "4.40.1", + "@rollup/rollup-linux-x64-musl": "4.40.1", + "@rollup/rollup-win32-arm64-msvc": "4.40.1", + "@rollup/rollup-win32-ia32-msvc": "4.40.1", + "@rollup/rollup-win32-x64-msvc": "4.40.1", "fsevents": "~2.3.2" } }, @@ -6216,15 +6217,12 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -6322,6 +6320,19 @@ "@img/sharp-win32-x64": "0.33.5" } }, + "node_modules/sharp/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", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6474,9 +6485,9 @@ "license": "MIT" }, "node_modules/smol-toml": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.3.tgz", - "integrity": "sha512-KMVLNWu490KlNfD0lbfDBUktJIEaZRBj1eeK0SMfdpO/rfyARIzlnPVI1Ge4l0vtSJmQUAiGKxMyLGrCT38iyA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz", + "integrity": "sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -6862,9 +6873,9 @@ "license": "0BSD" }, "node_modules/type-fest": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", - "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz", + "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -6977,9 +6988,9 @@ } }, "node_modules/unifont": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.2.0.tgz", - "integrity": "sha512-RoF14/tOhLvDa7R5K6A3PjsfJVFKvadvRpWjfV1ttabUe9704P1ie9z1ABLWEts/8SxrBVePav/XhgeFNltpsw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.4.1.tgz", + "integrity": "sha512-zKSY9qO8svWYns+FGKjyVdLvpGPwqmsCjeJLN1xndMiqxHWBAhoWDMYMG960MxeV48clBmG+fDP59dHY1VoZvg==", "license": "MIT", "dependencies": { "css-tree": "^3.0.0", @@ -7110,19 +7121,19 @@ } }, "node_modules/unstorage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.15.0.tgz", - "integrity": "sha512-m40eHdGY/gA6xAPqo8eaxqXgBuzQTlAKfmB1iF7oCKXE1HfwHwzDJBywK+qQGn52dta+bPlZluPF7++yR3p/bg==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.16.0.tgz", + "integrity": "sha512-WQ37/H5A7LcRPWfYOrDa1Ys02xAbpPJq6q5GkO88FBXVSQzHd7+BjEwfRqyaSWCv9MbsJy058GWjjPjcJ16GGA==", "license": "MIT", "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", - "destr": "^2.0.3", - "h3": "^1.15.0", + "destr": "^2.0.5", + "h3": "^1.15.2", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.6", "ofetch": "^1.4.1", - "ufo": "^1.5.4" + "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", @@ -7131,7 +7142,7 @@ "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6.0.3", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0", "@planetscale/database": "^1.19.0", @@ -7201,6 +7212,12 @@ } } }, + "node_modules/unstorage/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -7274,17 +7291,17 @@ } }, "node_modules/vite": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", - "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", + "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", - "tinyglobby": "^0.2.12" + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -7543,9 +7560,9 @@ } }, "node_modules/workerd": { - "version": "1.20250424.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250424.0.tgz", - "integrity": "sha512-3Nb69De9pfC21vLMW8Xpp5JXEPYd7e8MGcaEfo/6z1jOX9CFJVaqrAXr8RwYxDgN528ZahHqM51YQEcVlOu1Cw==", + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250428.0.tgz", + "integrity": "sha512-JJNWkHkwPQKQdvtM9UORijgYdcdJsihA4SfYjwh02IUQsdMyZ9jizV1sX9yWi9B9ptlohTW8UNHJEATuphGgdg==", "hasInstallScript": true, "license": "Apache-2.0", "bin": { @@ -7555,27 +7572,27 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250424.0", - "@cloudflare/workerd-darwin-arm64": "1.20250424.0", - "@cloudflare/workerd-linux-64": "1.20250424.0", - "@cloudflare/workerd-linux-arm64": "1.20250424.0", - "@cloudflare/workerd-windows-64": "1.20250424.0" + "@cloudflare/workerd-darwin-64": "1.20250428.0", + "@cloudflare/workerd-darwin-arm64": "1.20250428.0", + "@cloudflare/workerd-linux-64": "1.20250428.0", + "@cloudflare/workerd-linux-arm64": "1.20250428.0", + "@cloudflare/workerd-windows-64": "1.20250428.0" } }, "node_modules/wrangler": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.13.1.tgz", - "integrity": "sha512-ofF1QAoRYwmg/8ukoW6235ig2oGw187ETDN99ATIf+i0RZ+iYc+5ykzNxomY+T7fvRHKs+xh3at3LdFlwTdPQQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.14.1.tgz", + "integrity": "sha512-EU7IThP7i68TBftJJSveogvWZ5k/WRijcJh3UclDWiWWhDZTPbL6LOJEFhHKqFzHOaC4Y2Aewt48rfTz0e7oCw==", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.1", "blake3-wasm": "2.1.5", "esbuild": "0.25.2", - "miniflare": "4.20250424.0", + "miniflare": "4.20250428.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.15", - "workerd": "1.20250424.0" + "workerd": "1.20250428.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -7589,7 +7606,7 @@ "sharp": "^0.33.5" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250424.0" + "@cloudflare/workers-types": "^4.20250428.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { From f2c03736406fb7673c6fd30d677e481f56297b3c Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 08:39:31 -0400 Subject: [PATCH 09/16] refactor: update devcontainer and Biome configuration (#27) - Remove ESLint and Prettier in favor of Biome - Configure Biome as default formatter for TS/JS/JSON/MD - Add development helper extensions - Update VSCode settings for Biome integration Closes #27 --- .devcontainer/Dockerfile | 12 ++++++++++ .devcontainer/build-and-push.sh | 28 +++++++++++++++++++++++ .devcontainer/devcontainer.json | 40 ++++++++++++++++++++++++--------- .github/copilot-instructions.md | 26 +++++++++++++++++++++ 4 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/build-and-push.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..051457a --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye + +LABEL org.opencontainers.image.source="https://github.com/acedanger/finance" +LABEL org.opencontainers.image.description="Dev container for Finance App" + +# Install additional OS packages +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends git-core \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# Install global npm packages if needed +RUN su node -c "npm install -g typescript" \ No newline at end of file diff --git a/.devcontainer/build-and-push.sh b/.devcontainer/build-and-push.sh new file mode 100644 index 0000000..0828748 --- /dev/null +++ b/.devcontainer/build-and-push.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Configuration +GITHUB_USERNAME=$1 +IMAGE_NAME="finance-devcontainer" +IMAGE_TAG="latest" + +if [ -z "$GITHUB_USERNAME" ]; then + echo "Usage: $0 " + echo "Example: $0 acedanger" + exit 1 +fi + +FULL_IMAGE_NAME="ghcr.io/$GITHUB_USERNAME/$IMAGE_NAME:$IMAGE_TAG" + +# Build the image +echo "Building image: $FULL_IMAGE_NAME" +docker build -t $FULL_IMAGE_NAME -f Dockerfile . + +# Push to GitHub Container Registry +echo "Pushing image to GHCR..." +docker push $FULL_IMAGE_NAME + +echo "Done! Now update your devcontainer.json to use this image:" +echo "Replace the 'build' section with:" +echo '{ + "image": "'$FULL_IMAGE_NAME'" +}' \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8394ddb..a6615f6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,8 +1,8 @@ { "name": "Finance App Development", - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", - "workspaceFolder": "/home/acedanger/dev/finance", - "workspaceMount": "source=${localWorkspaceFolder},target=/home/acedanger/dev/finance,type=bind,consistency=cached", + "image": "ghcr.io/acedanger/finance-devcontainer:latest", + "workspaceFolder": "/workspaces/finance", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/finance,type=bind,consistency=cached", "features": { "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/docker-in-docker:2": {}, @@ -16,27 +16,39 @@ "GitHub.copilot", "GitHub.copilot-chat", "ms-vscode.vscode-typescript-next", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "biomejs.biome", + "PKief.material-icon-theme", + "Gruntfuggly.todo-tree", + "humao.rest-client" ], "settings": { "editor.formatOnSave": true, "[typescript]": { "editor.formatOnSave": true, - "editor.defaultFormatter": null, + "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" } }, "[javascript]": { "editor.formatOnSave": true, - "editor.defaultFormatter": null, + "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" } }, "[astro]": { "editor.defaultFormatter": "astro-build.astro-vscode" }, + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[markdown]": { + "editor.defaultFormatter": "biomejs.biome" + }, "typescript.updateImportsOnFileMove.enabled": "always", "editor.bracketPairColorization.enabled": true, "files.exclude": { @@ -48,7 +60,14 @@ "terminal.integrated.defaultProfile.linux": "bash", "mcp.servers.github": { "command": "docker", - "args": ["run", "-i", "--rm", "--env-file", "${containerWorkspaceFolder}/.devcontainer/.env", "ghcr.io/github/github-mcp-server"], + "args": [ + "run", + "-i", + "--rm", + "--env-file", + "${containerWorkspaceFolder}/.devcontainer/.env", + "ghcr.io/github/github-mcp-server" + ], "env": {} } } @@ -58,8 +77,9 @@ "postCreateCommand": "npm install && npm run check", "remoteUser": "node", "mounts": [ - "type=bind,source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,readonly" + "type=bind,source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/node/.ssh,readonly" ], + "updateRemoteUserUID": true, "remoteEnv": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${localEnv:GITHUB_PERSONAL_ACCESS_TOKEN}" }, diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 07d81cb..ee4401b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,6 +10,28 @@ This project is a web user interface (UI) for a CRUD (Create, Read, Update, Dele * **Language:** TypeScript, JavaScript (client-side scripts), HTML, CSS * **Styling:** Plain CSS (`src/styles/global.css`) * **Data:** Using Astro's built-in API routes in `src/pages/api/` with a temporary in-memory store (`src/data/store.ts`). **The goal is to eventually replace the in-memory store with a persistent database.** +* **Development Environment:** VS Code Dev Container using private Docker image (`ghcr.io/acedanger/finance-devcontainer:latest`) + +## Development Environment + +* **Dev Container:** The project uses a VS Code Dev Container for consistent development environments. + * Container configuration in `.devcontainer/devcontainer.json` + * Uses a private container image hosted on GitHub Container Registry + * Image: `ghcr.io/acedanger/finance-devcontainer:latest` + * Includes all necessary development tools and extensions + * Configured with GitHub CLI and authentication + * Features Docker-in-Docker support for additional container needs +* **Container Features:** + * Node.js and npm pre-installed + * Git and GitHub CLI configured + * TypeScript support + * VS Code extensions pre-configured + * Docker-in-Docker capability + * Automatic GitHub authentication +* **Authentication:** + * Requires GitHub Personal Access Token for private container access + * Token should be configured in `.devcontainer/.env` + * GitHub CLI authentication handled in post-start command ## Current State & Key Features @@ -36,6 +58,10 @@ This project is a web user interface (UI) for a CRUD (Create, Read, Update, Dele ## File Structure Overview +* `.devcontainer/`: Development container configuration + * `devcontainer.json`: VS Code Dev Container configuration + * `Dockerfile`: Base container definition (for reference) + * `.env.example`: Template for container environment variables * `src/components/`: Reusable UI components. * `src/data/`: Data store and persistence layer. * `src/layouts/`: Base page layout(s). From 58d8ebdfa190ea48f28e0f25fbae3c8447e24023 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 09:55:01 -0400 Subject: [PATCH 10/16] style: apply Biome formatting to TypeScript files (#27) - Fix import sorting - Standardize code formatting - Apply consistent TypeScript style - Update code to match Biome configuration Part of #27 --- src/components/AccountSummary.tsx | 9 +- src/components/AddTransactionForm.tsx | 15 +- src/components/TransactionTable.tsx | 20 +- src/pages/api/transactions/[id]/index.ts | 2 +- src/pages/api/transactions/index.ts | 2 +- src/server.ts | 347 ++++++++++++----------- src/test/accounts.test.ts | 6 +- src/test/setup.ts | 6 +- src/test/transactions.test.ts | 18 +- 9 files changed, 214 insertions(+), 211 deletions(-) diff --git a/src/components/AccountSummary.tsx b/src/components/AccountSummary.tsx index 96158f0..4c38849 100644 --- a/src/components/AccountSummary.tsx +++ b/src/components/AccountSummary.tsx @@ -1,12 +1,11 @@ -import React, { useState, useEffect } from 'react'; import { useStore } from '@nanostores/react'; +import type React from 'react'; +import { useEffect, useState } from 'react'; +import { currentAccountId as currentAccountIdStore, refreshKey } from '../stores/transactionStore'; import type { Account } from '../types'; import { formatCurrency } from '../utils'; -import { currentAccountId as currentAccountIdStore, refreshKey } from '../stores/transactionStore'; -interface AccountSummaryProps { - // No props needed, data comes from store and fetch -} +type AccountSummaryProps = {}; export default function AccountSummary({}: AccountSummaryProps) { const currentAccountId = useStore(currentAccountIdStore); diff --git a/src/components/AddTransactionForm.tsx b/src/components/AddTransactionForm.tsx index 03f6b1b..230838c 100644 --- a/src/components/AddTransactionForm.tsx +++ b/src/components/AddTransactionForm.tsx @@ -1,16 +1,17 @@ -import React, { useState, useEffect } from 'react'; import { useStore } from '@nanostores/react'; -import type { Transaction } from '../types'; +import type React from 'react'; +import { useEffect, useState } from 'react'; // Import store atoms and actions import { - currentAccountId as currentAccountIdStore, - transactionToEdit as transactionToEditStore, cancelEditingTransaction, + currentAccountId as currentAccountIdStore, transactionSaved, + transactionToEdit as transactionToEditStore, } from '../stores/transactionStore'; +import type { Transaction } from '../types'; // Remove props that now come from the store -interface AddTransactionFormProps {} +type AddTransactionFormProps = {}; export default function AddTransactionForm({}: AddTransactionFormProps) { // --- Read state from store --- @@ -87,7 +88,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) { if (!amount) { errors.push('Amount is required'); } else { - const amountNum = parseFloat(amount); + const amountNum = Number.parseFloat(amount); if (isNaN(amountNum)) { errors.push('Amount must be a valid number'); } else if (amountNum === 0) { @@ -134,7 +135,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) { accountId: currentAccountId, date: date, // Send as YYYY-MM-DD string description: description.trim(), - amount: parseFloat(amount), + amount: Number.parseFloat(amount), }; const method = editingId ? 'PUT' : 'POST'; diff --git a/src/components/TransactionTable.tsx b/src/components/TransactionTable.tsx index ff4ce84..4a19a79 100644 --- a/src/components/TransactionTable.tsx +++ b/src/components/TransactionTable.tsx @@ -1,15 +1,15 @@ -import React, { useState, useEffect } from 'react'; import { useStore } from '@nanostores/react'; +import React, { useState, useEffect } from 'react'; +import { + currentAccountId as currentAccountIdStore, + refreshKey, + startEditingTransaction, + triggerRefresh, +} from '../stores/transactionStore'; import type { Transaction } from '../types'; import { formatCurrency, formatDate } from '../utils'; -import { - startEditingTransaction, - currentAccountId as currentAccountIdStore, - triggerRefresh, - refreshKey, -} from '../stores/transactionStore'; -interface TransactionTableProps {} +type TransactionTableProps = {}; export default function TransactionTable({}: TransactionTableProps) { const currentAccountId = useStore(currentAccountIdStore); @@ -47,7 +47,7 @@ export default function TransactionTable({}: TransactionTableProps) { }, [currentAccountId, refreshCounter]); const sortedTransactions = [...transactions].sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), ); const handleDelete = async (txnId: string) => { @@ -76,7 +76,7 @@ export default function TransactionTable({}: TransactionTableProps) { console.log(`Transaction ${txnId} deleted successfully.`); setTransactions((currentTransactions) => - currentTransactions.filter((txn) => txn.id !== txnId) + currentTransactions.filter((txn) => txn.id !== txnId), ); triggerRefresh(); diff --git a/src/pages/api/transactions/[id]/index.ts b/src/pages/api/transactions/[id]/index.ts index 5fd1f24..e0c3266 100644 --- a/src/pages/api/transactions/[id]/index.ts +++ b/src/pages/api/transactions/[id]/index.ts @@ -1,5 +1,5 @@ import type { APIRoute } from 'astro'; -import { transactions, accounts } from '../../../../data/store'; +import { accounts, transactions } from '../../../../data/store'; import type { Transaction } from '../../../../types'; export const PUT: APIRoute = async ({ request, params }) => { diff --git a/src/pages/api/transactions/index.ts b/src/pages/api/transactions/index.ts index 212b461..57e1814 100644 --- a/src/pages/api/transactions/index.ts +++ b/src/pages/api/transactions/index.ts @@ -11,7 +11,7 @@ */ import type { APIRoute } from 'astro'; -import { transactions, accounts } from '../../../data/store'; +import { accounts, transactions } from '../../../data/store'; import type { Transaction } from '../../../types'; /** diff --git a/src/server.ts b/src/server.ts index 8b62898..52310d9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,11 +1,11 @@ // src/server.ts -import Fastify, { FastifyInstance } from 'fastify'; -import { Server, IncomingMessage, ServerResponse } from 'http'; -import { PrismaClient, BankAccount } from '@prisma/client'; +import { IncomingMessage, Server, ServerResponse } from 'http'; +import { type BankAccount, PrismaClient } from '@prisma/client'; import dotenv from 'dotenv'; +import Fastify, { type FastifyInstance } from 'fastify'; +import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import { z } from 'zod'; -import { ZodTypeProvider } from 'fastify-type-provider-zod'; dotenv.config(); @@ -13,9 +13,9 @@ const prisma = new PrismaClient(); // Base schema for common fields, useful for reuse const bankAccountBaseSchema = z.object({ - name: z.string().min(1, { message: "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" }), + name: z.string().min(1, { message: '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' }), }); // Schema for creating a bank account (all fields required) @@ -23,8 +23,8 @@ const createBankAccountSchema = bankAccountBaseSchema; // Schema for request parameters containing an ID const paramsSchema = z.object({ - // Use coerce to automatically convert string param to number - id: z.coerce.number().int().positive({ message: "ID must be a positive integer" }) + // Use coerce to automatically convert string param to number + id: z.coerce.number().int().positive({ message: 'ID must be a positive integer' }), }); // Schema for updating a bank account (all fields optional) @@ -32,7 +32,7 @@ const updateBankAccountSchema = bankAccountBaseSchema.partial(); // Makes all fi // --- Fastify Server Instance with Zod Type Provider --- const server: FastifyInstance = Fastify({ - logger: true + logger: true, }).withTypeProvider(); // Enable Zod validation and typing // --- API Routes --- @@ -41,206 +41,209 @@ const API_PREFIX = '/api/bank-account'; // 1. Create Bank Account server.post( - `${API_PREFIX}/create`, - { - schema: { // Define Zod schema for the request body - body: createBankAccountSchema - } + `${API_PREFIX}/create`, + { + schema: { + // Define Zod schema for the request body + body: createBankAccountSchema, }, - async (request, reply): Promise => { - try { - // request.body is now typed and validated by Zod! - const newAccount = await prisma.bankAccount.create({ - data: request.body, // Pass validated body directly - }); - reply.code(201); - return newAccount; - } catch (error: any) { - server.log.error(error); - if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { - reply.code(409); - const body = createBankAccountSchema.parse(request.body); - throw new Error(`Bank account with number ${body.accountNumber} already exists.`); - } - reply.code(500); - throw new Error('Failed to create bank account.'); - } + }, + async (request, reply): Promise => { + try { + // request.body is now typed and validated by Zod! + const newAccount = await prisma.bankAccount.create({ + data: request.body, // Pass validated body directly + }); + reply.code(201); + return newAccount; + } catch (error: any) { + server.log.error(error); + if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { + reply.code(409); + const body = createBankAccountSchema.parse(request.body); + throw new Error(`Bank account with number ${body.accountNumber} already exists.`); + } + reply.code(500); + throw new Error('Failed to create bank account.'); } + }, ); // 2. Update Bank Account server.post( - `${API_PREFIX}/update/:id`, - { - schema: { // Define Zod schemas for params and body - params: paramsSchema, - body: updateBankAccountSchema - } + `${API_PREFIX}/update/:id`, + { + schema: { + // Define Zod schemas for params and body + params: paramsSchema, + body: updateBankAccountSchema, }, - async (request, reply): Promise => { - try { - // request.params.id is now a validated number - // request.body is now a validated partial object - const { id } = request.params; - const updateData = request.body; + }, + async (request, reply): Promise => { + try { + // request.params.id is now a validated number + // request.body is now a validated partial object + const { id } = request.params; + const updateData = request.body; - // Prevent updating with an empty object - if (Object.keys(updateData).length === 0) { - reply.code(400); - throw new Error("Request body cannot be empty for update."); - } + // Prevent updating with an empty object + if (Object.keys(updateData).length === 0) { + reply.code(400); + throw new Error('Request body cannot be empty for update.'); + } - const updatedAccount = await prisma.bankAccount.update({ - where: { id: id }, // Use the validated numeric ID - data: updateData, - }); - return updatedAccount; - } catch (error: any) { - server.log.error(error); - if (error.code === 'P2025') { // Record to update not found - reply.code(404); - throw new Error(`Bank account with ID ${request.params.id} not found.`); - } - if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { - reply.code(409); - // Access accountNumber safely as it's optional in update - const attemptedNumber = request.body.accountNumber || '(unchanged)'; - throw new Error(`Bank account with number ${attemptedNumber} already exists.`); - } - // Handle Zod validation errors specifically if needed (though Fastify usually does) - if (error instanceof z.ZodError) { - reply.code(400); - throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`); - } - reply.code(500); - throw new Error('Failed to update bank account.'); - } + const updatedAccount = await prisma.bankAccount.update({ + where: { id: id }, // Use the validated numeric ID + data: updateData, + }); + return updatedAccount; + } catch (error: any) { + server.log.error(error); + if (error.code === 'P2025') { + // Record to update not found + reply.code(404); + throw new Error(`Bank account with ID ${request.params.id} not found.`); + } + if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) { + reply.code(409); + // Access accountNumber safely as it's optional in update + const attemptedNumber = request.body.accountNumber || '(unchanged)'; + throw new Error(`Bank account with number ${attemptedNumber} already exists.`); + } + // Handle Zod validation errors specifically if needed (though Fastify usually does) + if (error instanceof z.ZodError) { + reply.code(400); + throw new Error(`Validation Error: ${error.errors.map((e) => e.message).join(', ')}`); + } + reply.code(500); + throw new Error('Failed to update bank account.'); } + }, ); // 3. Delete Bank Account server.delete( - `${API_PREFIX}/delete/:id`, - { - schema: { // Define Zod schema for params - params: paramsSchema - } + `${API_PREFIX}/delete/:id`, + { + schema: { + // Define Zod schema for params + params: paramsSchema, }, - async (request, reply): Promise<{ message: string; deletedAccount: BankAccount }> => { - try { - // request.params.id is now a validated number - const { id } = request.params; + }, + async (request, reply): Promise<{ message: string; deletedAccount: BankAccount }> => { + try { + // request.params.id is now a validated number + const { id } = request.params; - const deletedAccount = await prisma.bankAccount.delete({ - where: { id: id }, // Use the validated numeric ID - }); - return { message: `Bank account with ID ${id} deleted successfully.`, deletedAccount }; - } catch (error: any) { - server.log.error(error); - if (error.code === 'P2025') { // Record to delete not found - reply.code(404); - throw new Error(`Bank account with ID ${request.params.id} not found.`); - } - // Handle Zod validation errors - if (error instanceof z.ZodError) { - reply.code(400); - throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`); - } - reply.code(500); - throw new Error('Failed to delete bank account.'); - } + const deletedAccount = await prisma.bankAccount.delete({ + where: { id: id }, // Use the validated numeric ID + }); + return { message: `Bank account with ID ${id} deleted successfully.`, deletedAccount }; + } catch (error: any) { + server.log.error(error); + if (error.code === 'P2025') { + // Record to delete not found + reply.code(404); + throw new Error(`Bank account with ID ${request.params.id} not found.`); + } + // Handle Zod validation errors + if (error instanceof z.ZodError) { + reply.code(400); + throw new Error(`Validation Error: ${error.errors.map((e) => e.message).join(', ')}`); + } + reply.code(500); + throw new Error('Failed to delete bank account.'); } + }, ); // 4. Get All Bank Accounts -server.get( - `${API_PREFIX}/`, - async (request, reply): Promise => { - // No input validation needed for getting all items usually - try { - const accounts = await prisma.bankAccount.findMany({ - orderBy: { createdAt: 'desc' } - }); - return accounts; - } catch (error: any) { - server.log.error(error); - reply.code(500); - throw new Error('Failed to retrieve bank accounts.'); - } - } -); +server.get(`${API_PREFIX}/`, async (request, reply): Promise => { + // No input validation needed for getting all items usually + try { + const accounts = await prisma.bankAccount.findMany({ + orderBy: { 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 server.get( - `${API_PREFIX}/:id`, - { - schema: { // Define Zod schema for params - params: paramsSchema - } + `${API_PREFIX}/:id`, + { + schema: { + // Define Zod schema for params + params: paramsSchema, }, - async (request, reply): Promise => { - try { - // request.params.id is now a validated number - const { id } = request.params; + }, + async (request, reply): Promise => { + try { + // request.params.id is now a validated number + const { id } = request.params; - const account = await prisma.bankAccount.findUnique({ - where: { id: id }, // Use the validated numeric ID - }); + const account = await prisma.bankAccount.findUnique({ + where: { id: id }, // Use the validated numeric ID + }); - if (!account) { - reply.code(404); - throw new Error(`Bank account with ID ${id} not found.`); - } - return account; - } catch (error: any) { - // Handle Zod validation errors (though should be caught by Fastify earlier) - if (error instanceof z.ZodError) { - reply.code(400); - throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`); - } - // If Prisma throws or other errors occur after validation - if (!reply.sent) { - // Specific check for Prisma's RecordNotFound (though findUnique returns null, not throws P2025 by default) - // The !account check above handles the "not found" case for findUnique + if (!account) { + reply.code(404); + throw new Error(`Bank account with ID ${id} not found.`); + } + return account; + } catch (error: any) { + // Handle Zod validation errors (though should be caught by Fastify earlier) + if (error instanceof z.ZodError) { + reply.code(400); + throw new Error(`Validation Error: ${error.errors.map((e) => e.message).join(', ')}`); + } + // If Prisma throws or other errors occur after validation + if (!reply.sent) { + // Specific check for Prisma's RecordNotFound (though findUnique returns null, not throws P2025 by default) + // The !account check above handles the "not found" case for findUnique - server.log.error(error); // Log other unexpected errors - reply.code(500); - throw new Error('Failed to retrieve bank account.'); - } - // If reply already sent (e.g., 404), just rethrow the original error - throw error; - } + server.log.error(error); // Log other unexpected errors + reply.code(500); + throw new Error('Failed to retrieve bank account.'); + } + // If reply already sent (e.g., 404), 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); - } + 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 (unchanged) --- const start = async () => { - try { - const host = process.env.API_HOST || '0.0.0.0'; - const port = parseInt(process.env.API_PORT || '3000', 10); - await server.listen({ port, host }); - } catch (err) { - server.log.error(err); - await prisma.$disconnect(); - process.exit(1); - } + try { + const host = process.env.API_HOST || '0.0.0.0'; + const port = Number.parseInt(process.env.API_PORT || '3000', 10); + await server.listen({ port, host }); + } catch (err) { + server.log.error(err); + await prisma.$disconnect(); + process.exit(1); + } }; start(); diff --git a/src/test/accounts.test.ts b/src/test/accounts.test.ts index 34b37e3..94aeb49 100644 --- a/src/test/accounts.test.ts +++ b/src/test/accounts.test.ts @@ -1,7 +1,7 @@ -import { describe, it, expect } from 'vitest'; -import { GET as listAccounts } from '../pages/api/accounts/index'; +import { describe, expect, it } from 'vitest'; import { GET as getAccount } from '../pages/api/accounts/[id]/index'; import { GET as listTransactions } from '../pages/api/accounts/[id]/transactions/index'; +import { GET as listAccounts } from '../pages/api/accounts/index'; import { createMockAPIContext } from './setup'; describe('Accounts API', () => { @@ -48,7 +48,7 @@ describe('Accounts API', () => { it('should return empty array for account with no transactions', async () => { const response = await listTransactions( - createMockAPIContext({ params: { id: '999' } }) as any + createMockAPIContext({ params: { id: '999' } }) as any, ); const transactions = await response.json(); diff --git a/src/test/setup.ts b/src/test/setup.ts index 9764888..2a41165 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,6 +1,6 @@ +import type { APIContext } from 'astro'; import { beforeEach } from 'vitest'; import { accounts, transactions } from '../data/store'; -import type { APIContext } from 'astro'; // Create a mock APIContext factory export function createMockAPIContext = Record>({ @@ -43,7 +43,7 @@ beforeEach(() => { name: 'Test Savings', last4: '5678', balance: 5000.0, - } + }, ); // Reset transactions to initial state @@ -62,6 +62,6 @@ beforeEach(() => { date: '2025-04-24', description: 'Test Transaction 2', amount: 100.0, - } + }, ); }); diff --git a/src/test/transactions.test.ts b/src/test/transactions.test.ts index bb5da92..ff659db 100644 --- a/src/test/transactions.test.ts +++ b/src/test/transactions.test.ts @@ -7,13 +7,13 @@ // - Add load testing for API endpoints // - Implement test data factories -import { describe, it, expect } from 'vitest'; -import { POST as createTransaction } from '../pages/api/transactions/index'; -import { - PUT as updateTransaction, - DELETE as deleteTransaction, -} from '../pages/api/transactions/[id]/index'; +import { describe, expect, it } from 'vitest'; import { accounts, transactions } from '../data/store'; +import { + DELETE as deleteTransaction, + PUT as updateTransaction, +} from '../pages/api/transactions/[id]/index'; +import { POST as createTransaction } from '../pages/api/transactions/index'; import type { Transaction } from '../types'; import { createMockAPIContext } from './setup'; @@ -273,7 +273,7 @@ describe('Transactions API', () => { const initialCount = transactions.length; const response = await deleteTransaction( - createMockAPIContext({ params: { id: '1' } }) as any + createMockAPIContext({ params: { id: '1' } }) as any, ); expect(response.status).toBe(204); @@ -292,7 +292,7 @@ describe('Transactions API', () => { it('should return 404 for non-existent transaction', async () => { const response = await deleteTransaction( - createMockAPIContext({ params: { id: '999' } }) as any + createMockAPIContext({ params: { id: '999' } }) as any, ); const error = await response.json(); @@ -313,7 +313,7 @@ describe('Transactions API', () => { transactions.push(testTransaction); const response = await deleteTransaction( - createMockAPIContext({ params: { id: 'test-delete' } }) as any + createMockAPIContext({ params: { id: 'test-delete' } }) as any, ); const error = await response.json(); From 1d540d1731fd96c5266d9378e70fba7ed2ca71f9 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 10:00:14 -0400 Subject: [PATCH 11/16] style: apply Biome formatting to Astro templates and CSS (#27) - Standardize HTML/CSS formatting - Fix template indentation - Apply consistent style to Astro components - Update component attributes formatting Part of #27 --- src/components/AccountSummary.astro | 2 +- src/components/MainContent.astro | 2 +- src/components/Sidebar.astro | 2 +- src/components/TransactionTable.astro | 4 ++-- src/pages/index.astro | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/AccountSummary.astro b/src/components/AccountSummary.astro index d15f22f..8cd4b6e 100644 --- a/src/components/AccountSummary.astro +++ b/src/components/AccountSummary.astro @@ -1,6 +1,6 @@ --- -import { formatCurrency } from '../utils'; import type { Account } from '../types'; +import { formatCurrency } from '../utils'; interface Props { account: Account; diff --git a/src/components/MainContent.astro b/src/components/MainContent.astro index 99a1570..663ef0f 100644 --- a/src/components/MainContent.astro +++ b/src/components/MainContent.astro @@ -1,6 +1,6 @@ --- -import TransactionTable from './TransactionTable.tsx'; import type { Account } from '../types'; +import TransactionTable from './TransactionTable.tsx'; interface Props { account: Account; diff --git a/src/components/Sidebar.astro b/src/components/Sidebar.astro index 4ec558f..bb84d1c 100644 --- a/src/components/Sidebar.astro +++ b/src/components/Sidebar.astro @@ -1,7 +1,7 @@ --- +import type { Account } from '../types'; import AccountSummary from './AccountSummary.tsx'; // Import the React component instead of the Astro one import AddTransactionForm from './AddTransactionForm.tsx'; -import type { Account } from '../types'; interface Props { accounts: Account[]; diff --git a/src/components/TransactionTable.astro b/src/components/TransactionTable.astro index 7f27a34..6968582 100644 --- a/src/components/TransactionTable.astro +++ b/src/components/TransactionTable.astro @@ -1,6 +1,6 @@ --- -import { formatCurrency, formatDate } from '../utils'; import type { Transaction } from '../types'; +import { formatCurrency, formatDate } from '../utils'; interface Props { transactions: Transaction[]; @@ -10,7 +10,7 @@ const { transactions } = Astro.props; // Sort transactions by date descending for display const sortedTransactions = [...transactions].sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), ); // TODO: UI/UX Improvements diff --git a/src/pages/index.astro b/src/pages/index.astro index 9202dd4..625b64d 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,7 +1,7 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro'; -import Sidebar from '../components/Sidebar.astro'; import MainContent from '../components/MainContent.astro'; +import Sidebar from '../components/Sidebar.astro'; +import BaseLayout from '../layouts/BaseLayout.astro'; import type { Account, Transaction } from '../types'; export interface Props { @@ -28,7 +28,7 @@ const initialAccount: Account = accounts[0] || { let initialTransactions: Transaction[] = []; if (initialAccount.id) { const transactionsResponse = await fetch( - `${baseUrl}/api/accounts/${initialAccount.id}/transactions` + `${baseUrl}/api/accounts/${initialAccount.id}/transactions`, ); initialTransactions = await transactionsResponse.json(); } From 4b87bce4b7f69f889f2b2985131cd31270184b5f Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 13:34:11 -0400 Subject: [PATCH 12/16] docs: add development container setup instructions (#29) - Add PAT requirements - Add devcontainer CLI commands - Add VS Code instructions - Add troubleshooting tips Part of #29 --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5509c4c..bb2dbdf 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,40 @@ The MCP server will automatically start when you open the project in a devcontai 1. Run `gh auth login` in the terminal 2. Follow the prompts to authenticate with your GitHub account -### Database Setup \ No newline at end of file +### Database Setup + +## Development Container Setup + +### GitHub Personal Access Token +Before using the development container, you'll need a GitHub Personal Access Token (PAT) with the following permissions: +- `repo` (Full control of private repositories) +- `read:packages` (Download container images) +- `write:packages` (Upload container images) +- `delete:packages` (Optional: manage container versions) +- `workflow` (GitHub Actions integration) + +### Using the Development Container + +#### Command Line Interface +You can build and start the development container using the devcontainer CLI: + +```bash +# Build the container +devcontainer build . + +# Start the container (with post-create command) +devcontainer up --workspace-folder . + +# Start the container (skip post-create) +devcontainer up --workspace-folder . --skip-post-create +``` + +#### VS Code +1. Open the project in VS Code +2. Press F1 and run "Dev Containers: Rebuild and Reopen in Container" + - To skip post-create steps: Press F1 and run "Dev Containers: Rebuild Container Without Cache" + +### Troubleshooting +- If GitHub authentication fails, ensure your PAT is correctly set in `.devcontainer/.env` +- For network issues, try rebuilding the container with `--no-cache` option +- Check the VS Code "Dev Containers" output panel for detailed logs \ No newline at end of file From c06b73a1f30661dbc9c6cb04afaee95cd405aa69 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 13:38:15 -0400 Subject: [PATCH 13/16] feat: enhance devcontainer configuration with network settings and host aliases. Part of #27 --- .devcontainer/devcontainer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a6615f6..c870af1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,10 @@ "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, - "runArgs": ["--dns", "8.8.8.8", "--dns", "8.8.4.4"], + "runArgs": ["--dns", "8.8.8.8", "--dns", "8.8.4.4", "--network=host", "--dns-search=."], + "containerEnv": { + "HOSTALIASES": "/etc/host.aliases" + }, "customizations": { "vscode": { "extensions": [ From febbd943b2279d6d650e1bc66a27714a7220758f Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 19:56:16 -0400 Subject: [PATCH 14/16] feat: enhance devcontainer build script with better error handling (#29) - Add tool availability checks - Add GitHub authentication verification - Improve error messages and user feedback - Add clear next steps for users Part of #29 --- .devcontainer/build-and-push.sh | 61 ++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/.devcontainer/build-and-push.sh b/.devcontainer/build-and-push.sh index 0828748..721a0b9 100644 --- a/.devcontainer/build-and-push.sh +++ b/.devcontainer/build-and-push.sh @@ -1,28 +1,73 @@ #!/bin/bash +# Exit on error, undefined variables, and pipe failures +set -euo pipefail + # Configuration GITHUB_USERNAME=$1 IMAGE_NAME="finance-devcontainer" IMAGE_TAG="latest" -if [ -z "$GITHUB_USERNAME" ]; then +# Check for required username argument +if [ -z "${GITHUB_USERNAME:-}" ]; then + echo "Error: GitHub username is required" echo "Usage: $0 " echo "Example: $0 acedanger" exit 1 fi +# Check for required tools +for cmd in docker gh; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: $cmd is required but not installed" + exit 1 + fi +done + +# Check GitHub authentication +if ! gh auth status >/dev/null 2>&1; then + echo "Error: Not authenticated with GitHub. Please run 'gh auth login' first" + exit 1 +fi + FULL_IMAGE_NAME="ghcr.io/$GITHUB_USERNAME/$IMAGE_NAME:$IMAGE_TAG" +echo "=== Building Development Container ===" +echo "Username: $GITHUB_USERNAME" +echo "Image: $FULL_IMAGE_NAME" + # Build the image -echo "Building image: $FULL_IMAGE_NAME" -docker build -t $FULL_IMAGE_NAME -f Dockerfile . +echo -e "\n=> Building image..." +if ! docker build -t "$FULL_IMAGE_NAME" -f Dockerfile .; then + echo "Error: Docker build failed" + exit 1 +fi + +# Ensure logged into GitHub Container Registry +echo -e "\n=> Ensuring GitHub Container Registry access..." +if ! docker login ghcr.io -u "$GITHUB_USERNAME"; then + echo "Error: Failed to authenticate with GitHub Container Registry" + exit 1 +fi # Push to GitHub Container Registry -echo "Pushing image to GHCR..." -docker push $FULL_IMAGE_NAME +echo -e "\n=> Pushing image to GitHub Container Registry..." +if ! docker push "$FULL_IMAGE_NAME"; then + echo "Error: Failed to push image" + echo "Please check your GitHub PAT has the required permissions:" + echo " - read:packages" + echo " - write:packages" + exit 1 +fi -echo "Done! Now update your devcontainer.json to use this image:" -echo "Replace the 'build' section with:" +echo -e "\n=== Success! ===" +echo "The development container image has been built and pushed" +echo -e "\nTo use this image, update your devcontainer.json with:" echo '{ "image": "'$FULL_IMAGE_NAME'" -}' \ No newline at end of file +}' + +echo -e "\nNext steps:" +echo "1. Update .devcontainer/devcontainer.json with the image reference above" +echo "2. Rebuild your development container in VS Code" +echo " (Command Palette -> 'Dev Containers: Rebuild Container')" \ No newline at end of file From a298de5af9137966a8e2c2b3e2b4fb36a594f12f Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 21:09:44 -0400 Subject: [PATCH 15/16] feat: add PowerShell build script for Windows users (#29) - Add build-and-push.ps1 script for Windows users - Update ENVIRONMENT_SETUP.md with cross-platform build instructions - Improve error handling and feedback in build scripts - Add proper Windows path handling Part of #29 --- .devcontainer/build-and-push.ps1 | 115 +++++++++++++++++++++++ ENVIRONMENT_SETUP.md | 155 +++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 .devcontainer/build-and-push.ps1 create mode 100644 ENVIRONMENT_SETUP.md diff --git a/.devcontainer/build-and-push.ps1 b/.devcontainer/build-and-push.ps1 new file mode 100644 index 0000000..6544b0a --- /dev/null +++ b/.devcontainer/build-and-push.ps1 @@ -0,0 +1,115 @@ +# Requires -Version 5.0 +param( + [Parameter(Mandatory=$true)] + [string]$GitHubUsername +) + +# Stop on first error +$ErrorActionPreference = "Stop" + +# Configuration +$ImageName = "finance-devcontainer" +$ImageTag = "latest" +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$DockerfilePath = Join-Path $ScriptDir "Dockerfile" +$EnvFile = Join-Path $ScriptDir ".env" +$FullImageName = "ghcr.io/$GitHubUsername/$ImageName`:$ImageTag" + +# Function to check required tools +function Test-RequiredTools { + $tools = @("docker", "gh") + foreach ($tool in $tools) { + if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { + Write-Error "Error: $tool is required but not installed" + exit 1 + } + } +} + +# Function to load environment variables from .env file +function Get-EnvContent { + if (Test-Path $EnvFile) { + Write-Host "Loading environment from $EnvFile" + $envContent = Get-Content $EnvFile + foreach ($line in $envContent) { + if ($line -match '^GITHUB_PERSONAL_ACCESS_TOKEN=(.*)$') { + return $matches[1] + } + } + } + Write-Error "Error: GITHUB_PERSONAL_ACCESS_TOKEN not found in $EnvFile" + exit 1 +} + +# Verify prerequisites +Write-Host "=== Building Development Container ===" +Write-Host "Username: $GitHubUsername" +Write-Host "Image: $FullImageName" +Write-Host "Dockerfile: $DockerfilePath" + +# Check required tools +Test-RequiredTools + +# Check GitHub authentication +try { + gh auth status +} catch { + Write-Error "Error: Not authenticated with GitHub. Please run 'gh auth login' first" + exit 1 +} + +# Get GitHub PAT from .env file +$GitHubPAT = Get-EnvContent + +if ([string]::IsNullOrEmpty($GitHubPAT)) { + Write-Error "Error: GITHUB_PERSONAL_ACCESS_TOKEN is empty" + exit 1 +} + +Write-Host "Using PAT: $($GitHubPAT.Substring(0, 4))... (first 4 chars)" + +# Build the image +Write-Host "`n=> Building image..." +try { + docker build -t $FullImageName -f $DockerfilePath $ScriptDir +} catch { + Write-Error "Error: Docker build failed" + exit 1 +} + +# Log in to GitHub Container Registry +Write-Host "`n=> Logging into GitHub Container Registry..." +$GitHubPAT | docker login ghcr.io -u $GitHubUsername --password-stdin + +if ($LASTEXITCODE -ne 0) { + Write-Error "Error: Failed to authenticate with GitHub Container Registry" + exit 1 +} + +# Push to GitHub Container Registry +Write-Host "`n=> Pushing image to GitHub Container Registry..." +docker push $FullImageName + +if ($LASTEXITCODE -ne 0) { + Write-Error @" +Error: Failed to push image +Please check your GitHub PAT has the required permissions: + - read:packages + - write:packages +"@ + exit 1 +} + +Write-Host "`n=== Success! ===" +Write-Host "The development container image has been built and pushed" +Write-Host "`nTo use this image, update your devcontainer.json with:" +Write-Host @" +{ + "image": "$FullImageName" +} +"@ + +Write-Host "`nNext steps:" +Write-Host "1. Update .devcontainer/devcontainer.json with the image reference above" +Write-Host "2. Rebuild your development container in VS Code" +Write-Host " (Command Palette -> 'Dev Containers: Rebuild Container')" \ No newline at end of file diff --git a/ENVIRONMENT_SETUP.md b/ENVIRONMENT_SETUP.md new file mode 100644 index 0000000..c2c6676 --- /dev/null +++ b/ENVIRONMENT_SETUP.md @@ -0,0 +1,155 @@ +# Development Environment Setup Guide + +## Prerequisites + +- Windows, macOS, or Linux +- Git +- Docker Desktop +- Visual Studio Code with Remote - Containers extension +- GitHub CLI + +## Initial Setup + +1. **Clone the Repository** + ```bash + git clone https://github.com/acedanger/finance.git + cd finance + ``` + +2. **Create Environment Files** + ```bash + cp .devcontainer/.env.example .devcontainer/.env + cp .env.example .env + ``` + +## GitHub Personal Access Token (PAT) + +1. **Create a new PAT:** + - Go to GitHub Settings > Developer settings > Personal access tokens > Tokens (classic) + - Click "Generate new token (classic)" + - Name: `finance-dev-token` + - Set expiration: 90 days (recommended) + - Required scopes: + - `repo` (Full control of private repositories) + - `read:packages` (Download container images) + - `write:packages` (Upload container images) + - `delete:packages` (Optional: manage container versions) + - `workflow` (GitHub Actions integration) + +2. **Configure the PAT:** + - Open `.devcontainer/.env` + - Set `GITHUB_PERSONAL_ACCESS_TOKEN=your_token_here` + +## Building the Development Container + +### Using VS Code (Recommended) +1. Open the project in VS Code +2. When prompted, click "Reopen in Container" + - Or press F1 and run "Dev Containers: Rebuild and Reopen in Container" + +### Using Command Line +1. Install the devcontainer CLI: + ```bash + npm install -g @devcontainers/cli + ``` + +2. Build the container: + ```bash + devcontainer build . + ``` + +3. Start the container: + ```bash + # With post-create command + devcontainer up --workspace-folder . + + # Skip post-create command + devcontainer up --workspace-folder . --skip-post-create + ``` + +## Building and Pushing the Container Image + +1. Ensure you're in the `.devcontainer` directory + +2. Choose your preferred script: + + **Using PowerShell:** + ```powershell + .\build-and-push.ps1 your_github_username + ``` + + **Using Git Bash/Unix Shell:** + ```bash + chmod +x build-and-push.sh + ./build-and-push.sh your_github_username + ``` + +Both scripts perform the same functions: +- Validate prerequisites (Docker, GitHub CLI) +- Load GitHub PAT from `.env` file +- Build the container image +- Push to GitHub Container Registry +- Provide next steps for using the image + +## Environment Files + +The project uses two separate environment files: + +1. `.devcontainer/.env`: + - Container-specific configuration + - GitHub PAT + - PostgreSQL settings + - pgAdmin settings + +2. `.env`: + - Application-specific configuration + - Development server port + - API base URL + - Node environment + +## Troubleshooting + +### Container Build Issues +- Ensure Docker Desktop is running +- Check that your PAT has the correct permissions +- Try rebuilding without cache: `devcontainer build . --no-cache` + +### GitHub Authentication Issues +- Verify your PAT in `.devcontainer/.env` +- Try logging in manually: `gh auth login` +- Check GitHub CLI status: `gh auth status` + +### Network Issues +- If DNS resolution fails, try using different DNS servers in `devcontainer.json` +- Check if GitHub Container Registry (ghcr.io) is accessible +- Verify Docker network settings + +### VS Code Issues +- Install/update the Remote - Containers extension +- Clear VS Code's container cache +- Check VS Code's "Dev Containers" output panel for detailed logs + +## Common Operations + +### Rebuilding the Container +```bash +# Using VS Code +F1 -> "Dev Containers: Rebuild Container" + +# Using CLI +devcontainer build . --no-cache +``` + +### Updating the Container Image +1. Make changes to `Dockerfile` or `devcontainer.json` +2. Run the build script: + ```bash + ./build-and-push.sh your_github_username + ``` +3. Update image reference in `devcontainer.json` +4. Rebuild container in VS Code + +### Starting Development Server +```bash +npm run dev +``` \ No newline at end of file From bf82354ff35f9ab8f0520c5258dd827063e7ebd2 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 4 May 2025 21:16:40 -0400 Subject: [PATCH 16/16] refactor: improve devcontainer configuration and documentation (#29) - Fix container image labels to link with GitHub repository - Enhance build script with better error handling - Update documentation with cross-platform build instructions Part of #29 --- .devcontainer/Dockerfile | 4 ++-- .devcontainer/build-and-push.sh | 33 ++++++++++++++++++++++++++------- README.md | 11 +---------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 051457a..9c1dd63 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ FROM mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye -LABEL org.opencontainers.image.source="https://github.com/acedanger/finance" -LABEL org.opencontainers.image.description="Dev container for Finance App" +LABEL org.opencontainers.image.source=https://github.com/acedanger/finance +LABEL org.opencontainers.image.description="Development container for Finance application with Node.js, TypeScript, and dev tools" # Install additional OS packages RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ diff --git a/.devcontainer/build-and-push.sh b/.devcontainer/build-and-push.sh index 721a0b9..09a85bf 100644 --- a/.devcontainer/build-and-push.sh +++ b/.devcontainer/build-and-push.sh @@ -8,6 +8,15 @@ GITHUB_USERNAME=$1 IMAGE_NAME="finance-devcontainer" IMAGE_TAG="latest" +# Load environment variables from .env file if it exists +ENV_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)/.env" +if [ -f "$ENV_FILE" ]; then + echo "Loading environment from $ENV_FILE" + # Use grep to find the PAT line and extract the value, handling both Unix and Windows line endings + GITHUB_PERSONAL_ACCESS_TOKEN=$(grep -a "^GITHUB_PERSONAL_ACCESS_TOKEN=" "$ENV_FILE" | sed 's/^GITHUB_PERSONAL_ACCESS_TOKEN=//' | tr -d '\r') + export GITHUB_PERSONAL_ACCESS_TOKEN +fi + # Check for required username argument if [ -z "${GITHUB_USERNAME:-}" ]; then echo "Error: GitHub username is required" @@ -24,31 +33,41 @@ for cmd in docker gh; do fi done +# Verify PAT is loaded +if [ -z "${GITHUB_PERSONAL_ACCESS_TOKEN:-}" ]; then + echo "Error: GITHUB_PERSONAL_ACCESS_TOKEN is not set" + echo "Please ensure it is defined in $ENV_FILE" + exit 1 +fi + # Check GitHub authentication if ! gh auth status >/dev/null 2>&1; then echo "Error: Not authenticated with GitHub. Please run 'gh auth login' first" exit 1 fi +# Get absolute path to Dockerfile +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" +DOCKERFILE_PATH="$SCRIPT_DIR/Dockerfile" + FULL_IMAGE_NAME="ghcr.io/$GITHUB_USERNAME/$IMAGE_NAME:$IMAGE_TAG" echo "=== Building Development Container ===" echo "Username: $GITHUB_USERNAME" echo "Image: $FULL_IMAGE_NAME" +echo "Dockerfile: $DOCKERFILE_PATH" +echo "Using PAT: ${GITHUB_PERSONAL_ACCESS_TOKEN:0:4}... (first 4 chars)" # Build the image echo -e "\n=> Building image..." -if ! docker build -t "$FULL_IMAGE_NAME" -f Dockerfile .; then +if ! docker build -t "$FULL_IMAGE_NAME" -f "$DOCKERFILE_PATH" "$SCRIPT_DIR"; then echo "Error: Docker build failed" exit 1 fi -# Ensure logged into GitHub Container Registry -echo -e "\n=> Ensuring GitHub Container Registry access..." -if ! docker login ghcr.io -u "$GITHUB_USERNAME"; then - echo "Error: Failed to authenticate with GitHub Container Registry" - exit 1 -fi +# Log in to GitHub Container Registry +echo -e "\n=> Logging into GitHub Container Registry..." +echo "$GITHUB_PERSONAL_ACCESS_TOKEN" | docker login ghcr.io -u "$GITHUB_USERNAME" --password-stdin # Push to GitHub Container Registry echo -e "\n=> Pushing image to GitHub Container Registry..." diff --git a/README.md b/README.md index bb2dbdf..20b3dc5 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,7 @@ This app is currently deployed using Cloudflare Pages. The logs can be viewed wi ## Development Environment Setup -### Prerequisites -- VS Code with Remote Containers extension -- Docker and Docker Compose -- Git - -### Initial Setup -1. Clone the repository -2. Copy `.devcontainer/.env.example` to `.devcontainer/.env` -3. Update the environment variables in `.devcontainer/.env` -4. Open the project in VS Code and select "Reopen in Container" when prompted +For detailed setup instructions, including container building, environment configuration, and troubleshooting, see [ENVIRONMENT_SETUP.md](ENVIRONMENT_SETUP.md). ### GitHub MCP Server The project uses GitHub's MCP server for development tasks. The server runs in a Docker container and is automatically configured when you open the project in a devcontainer.