From fabea404efbcd33927d400582e4ac5e928ff3828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elisi=C3=A1rio=20Couto?= Date: Sun, 7 Dec 2025 00:54:51 +0000 Subject: [PATCH] refactor: Remove API response wrapper pattern. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace wrapped responses {success, data, message} with direct data returns following REST best practices. Simplifies 41 endpoints across 7 route files and updates all 109 tests. Also fixes test config setup to not require user home directory config file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 --- frontend/package-lock.json | 1176 +++++++++++++++++++-------- frontend/src/lib/api.ts | 138 ++-- frontend/src/types/api.ts | 16 +- leggen/api/models/common.py | 32 +- leggen/api/routes/accounts.py | 72 +- leggen/api/routes/backup.py | 90 +- leggen/api/routes/banks.py | 50 +- leggen/api/routes/notifications.py | 65 +- leggen/api/routes/sync.py | 97 +-- leggen/api/routes/transactions.py | 51 +- leggen/commands/server.py | 28 +- tests/conftest.py | 47 +- tests/unit/test_analytics_fix.py | 6 +- tests/unit/test_api_accounts.py | 37 +- tests/unit/test_api_backup.py | 31 +- tests/unit/test_api_banks.py | 24 +- tests/unit/test_api_transactions.py | 46 +- 17 files changed, 1171 insertions(+), 835 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e0193d1..8419f48 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2009,9 +2009,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", "cpu": [ "ppc64" ], @@ -2025,9 +2025,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", "cpu": [ "arm" ], @@ -2041,9 +2041,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", "cpu": [ "arm64" ], @@ -2057,9 +2057,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", "cpu": [ "x64" ], @@ -2073,9 +2073,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", "cpu": [ "arm64" ], @@ -2089,9 +2089,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", "cpu": [ "x64" ], @@ -2105,9 +2105,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", "cpu": [ "arm64" ], @@ -2121,9 +2121,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", "cpu": [ "x64" ], @@ -2137,9 +2137,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", "cpu": [ "arm" ], @@ -2153,9 +2153,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", "cpu": [ "arm64" ], @@ -2169,9 +2169,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", "cpu": [ "ia32" ], @@ -2185,9 +2185,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", "cpu": [ "loong64" ], @@ -2201,9 +2201,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", "cpu": [ "mips64el" ], @@ -2217,9 +2217,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", "cpu": [ "ppc64" ], @@ -2233,9 +2233,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", "cpu": [ "riscv64" ], @@ -2249,9 +2249,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", "cpu": [ "s390x" ], @@ -2265,9 +2265,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", "cpu": [ "x64" ], @@ -2281,9 +2281,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", "cpu": [ "arm64" ], @@ -2297,9 +2297,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", "cpu": [ "x64" ], @@ -2313,9 +2313,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", "cpu": [ "arm64" ], @@ -2329,9 +2329,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", "cpu": [ "x64" ], @@ -2345,9 +2345,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", "cpu": [ "arm64" ], @@ -2361,9 +2361,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", "cpu": [ "x64" ], @@ -2377,9 +2377,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", "cpu": [ "arm64" ], @@ -2393,9 +2393,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", "cpu": [ "ia32" ], @@ -2409,9 +2409,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", "cpu": [ "x64" ], @@ -2508,9 +2508,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2520,7 +2520,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -3403,9 +3403,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz", - "integrity": "sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==", + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz", + "integrity": "sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==", "dev": true, "license": "MIT", "dependencies": { @@ -3418,20 +3418,25 @@ "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@cfworker/json-schema": "^4.1.1" + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { "@cfworker/json-schema": { "optional": true + }, + "zod": { + "optional": false } } }, @@ -3580,13 +3585,13 @@ "license": "MIT" }, "node_modules/@quansync/fs": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.5.tgz", - "integrity": "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", "dev": true, "license": "MIT", "dependencies": { - "quansync": "^0.2.11" + "quansync": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/sxzz" @@ -6035,9 +6040,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.10.tgz", - "integrity": "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==", + "version": "5.90.12", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz", + "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==", "license": "MIT", "funding": { "type": "github", @@ -6045,12 +6050,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.10.tgz", - "integrity": "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==", + "version": "5.90.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz", + "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.10" + "@tanstack/query-core": "5.90.12" }, "funding": { "type": "github", @@ -6061,14 +6066,14 @@ } }, "node_modules/@tanstack/react-router": { - "version": "1.139.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.139.3.tgz", - "integrity": "sha512-lhqK0DnbA7PgHOnmhzOoWVzx8qd8oEpR4cOUbxAjwb3+ExFQWrEvRf9+ZdSxs49ZrtZL2S2UltxBv3vBV4Si5g==", + "version": "1.139.14", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.139.14.tgz", + "integrity": "sha512-eNQvFu2F+7tjCRLUiXWCHZv5OhNjn/0LP6k7o5IiOg5+JR1TOu2ztxhk1EqZfBHrebuenTFQHyFXfXVDi+3wkA==", "license": "MIT", "dependencies": { "@tanstack/history": "1.139.0", "@tanstack/react-store": "^0.8.0", - "@tanstack/router-core": "1.139.3", + "@tanstack/router-core": "1.139.14", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" @@ -6124,12 +6129,12 @@ } }, "node_modules/@tanstack/router-cli": { - "version": "1.139.3", - "resolved": "https://registry.npmjs.org/@tanstack/router-cli/-/router-cli-1.139.3.tgz", - "integrity": "sha512-HGTe7N0AzXFuteTDRj3RZT3ZfAVJ+XI5uaLGVHMQ0Cmc4wma0k1yyurKDVjsFFLeZk2NZQ+RcaHhNiS8iHRTwQ==", + "version": "1.139.14", + "resolved": "https://registry.npmjs.org/@tanstack/router-cli/-/router-cli-1.139.14.tgz", + "integrity": "sha512-SHN7SV9VuVx6M5TQTcgPb8e7BwASfqFpfYT4QHPrlKJhikxvk1tP4uO6lb4mrdYiQ6mC/dY/qvYpc9u77VY8ow==", "license": "MIT", "dependencies": { - "@tanstack/router-generator": "1.139.3", + "@tanstack/router-generator": "1.139.14", "chokidar": "^3.6.0", "yargs": "^17.7.2" }, @@ -6145,9 +6150,9 @@ } }, "node_modules/@tanstack/router-core": { - "version": "1.139.3", - "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.139.3.tgz", - "integrity": "sha512-j3v1e739jmozBdtnmA45xHQHjCC2aKqBtfkMT3t2ZPijVrueaVP6qNRIAWmDK4ZSqd67TF5wP8vyqeTShJsEQQ==", + "version": "1.139.14", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.139.14.tgz", + "integrity": "sha512-OjNeTlAti75G+8djiAaQsfio4mpnn9HBFfION15nzIgmv+VX6wOS/OyOYKkaKf+QSecXcjajyV3HHc8YornH/A==", "license": "MIT", "dependencies": { "@tanstack/history": "1.139.0", @@ -6167,12 +6172,12 @@ } }, "node_modules/@tanstack/router-generator": { - "version": "1.139.3", - "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.139.3.tgz", - "integrity": "sha512-zq/ZC+1rx7pGNqYOthHSm0jxioNqm7JszYDcZPAZVhHT2Qhen02b7PzW8M7Qx4FU+ldXgnhpenDN5/jqYYClfQ==", + "version": "1.139.14", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.139.14.tgz", + "integrity": "sha512-qRFOVyKph4I3j3c91W6jQLe3vuD4xHSUwZ9wWuIm+uk1NAOfwi2UBGhbzjLGSnRMtOVCHVLqD60sxaRvyZe7zQ==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.139.3", + "@tanstack/router-core": "1.139.14", "@tanstack/router-utils": "1.139.0", "@tanstack/virtual-file-routes": "1.139.0", "prettier": "^3.5.0", @@ -6190,9 +6195,9 @@ } }, "node_modules/@tanstack/router-plugin": { - "version": "1.139.3", - "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.139.3.tgz", - "integrity": "sha512-ymv5mr2IULgrZblzYeYUVZXzz3fzW1OzoDZh5cs5gEetRcEpXQ6XL8KG4pCkIQ04AJcrtXEWt1yZxi01XjZWxw==", + "version": "1.139.14", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.139.14.tgz", + "integrity": "sha512-jmDY5aF7ivjKKdBF8+VNSKmMpX9yJU5SiqoKbSvTJ715XVwxqKVKhsW2oaT67q0NhesSUsJ7LciJdt3tZb+8zQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6202,8 +6207,8 @@ "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", - "@tanstack/router-core": "1.139.3", - "@tanstack/router-generator": "1.139.3", + "@tanstack/router-core": "1.139.14", + "@tanstack/router-generator": "1.139.14", "@tanstack/router-utils": "1.139.0", "@tanstack/virtual-file-routes": "1.139.0", "babel-dead-code-elimination": "^1.0.10", @@ -6220,7 +6225,7 @@ }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", - "@tanstack/react-router": "^1.139.3", + "@tanstack/react-router": "^1.139.14", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" @@ -6267,13 +6272,13 @@ } }, "node_modules/@tanstack/router-vite-plugin": { - "version": "1.139.3", - "resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.139.3.tgz", - "integrity": "sha512-UXL4emf+essF5ToeVztYMTXOJZ0c8yN9G8UeQAwTX7LEdZOe++m76be0/qMqV3mlqMigrcFkDjExJQzbj/MjPQ==", + "version": "1.139.14", + "resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.139.14.tgz", + "integrity": "sha512-NQaaUoYTBOU1qtQlRJSYJNmd+ZCqNg4GnHN4F5Fsc399TQBj4TWyF2OOQtp8fb8HbTuCBy3k8qXOyj3bBl6J+g==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/router-plugin": "1.139.3" + "@tanstack/router-plugin": "1.139.14" }, "engines": { "node": ">=12" @@ -6470,9 +6475,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", - "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6511,17 +6516,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", - "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz", + "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/type-utils": "8.47.0", - "@typescript-eslint/utils": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/type-utils": "8.48.1", + "@typescript-eslint/utils": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -6535,7 +6540,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.47.0", + "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -6551,16 +6556,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz", - "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz", + "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "engines": { @@ -6576,14 +6581,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", - "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", + "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.47.0", - "@typescript-eslint/types": "^8.47.0", + "@typescript-eslint/tsconfig-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "engines": { @@ -6598,14 +6603,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", - "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", + "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0" + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6616,9 +6621,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", - "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", + "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", "dev": true, "license": "MIT", "engines": { @@ -6633,15 +6638,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz", - "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", + "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/utils": "8.47.0", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -6658,9 +6663,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz", - "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", + "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", "dev": true, "license": "MIT", "engines": { @@ -6672,21 +6677,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz", - "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", + "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.47.0", - "@typescript-eslint/tsconfig-utils": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", + "@typescript-eslint/project-service": "8.48.1", + "@typescript-eslint/tsconfig-utils": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -6740,16 +6744,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", - "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", + "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0" + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6764,13 +6768,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz", - "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", + "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -7684,9 +7688,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", - "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz", + "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -7705,24 +7709,28 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { @@ -7749,9 +7757,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -7768,11 +7776,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -7877,9 +7885,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001756", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", - "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "funding": [ { "type": "opencollective", @@ -8815,9 +8823,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -8990,9 +8998,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -9002,32 +9010,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" } }, "node_modules/escalade": { @@ -9309,9 +9317,9 @@ } }, "node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -9336,19 +9344,20 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -9592,9 +9601,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", "dependencies": { @@ -9606,7 +9615,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { @@ -10268,9 +10281,9 @@ "license": "MPL-2.0" }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10278,6 +10291,10 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/idb": { @@ -10946,6 +10963,16 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11389,9 +11416,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.3.tgz", - "integrity": "sha512-/5rpGC0eK8LlFqsHaBmL19/PVKxu/CCt8pO1vzp9X6SDLsRDh/Ccudkf3Ur5lyaKxJz9ndAx+LaThdv0ySqB6A==", + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.4.tgz", + "integrity": "sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -11434,13 +11461,17 @@ } }, "node_modules/msw/node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mute-stream": { @@ -11834,9 +11865,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", - "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "dev": true, "license": "MIT" }, @@ -11946,11 +11977,11 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -12191,9 +12222,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -12322,9 +12353,9 @@ } }, "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", "dev": true, "funding": [ { @@ -12394,36 +12425,19 @@ "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-day-picker": { - "version": "9.11.2", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.2.tgz", - "integrity": "sha512-TD/xMUGg2oiKX8jUR21MST5pj+7Y36097YtnDHQFlIcZOu3mbLLw2B2JqEByEGrR3HHveWYnKlyls6WqJgohAg==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.12.0.tgz", + "integrity": "sha512-t8OvG/Zrciso5CQJu5b1A7yzEmebvST+S3pOVQJWxwjjVngyG/CA2htN/D15dLI4uTEuLLkbZyS4YYt480FAtA==", "license": "MIT", "dependencies": { "@date-fns/tz": "^1.4.1", @@ -12442,15 +12456,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.0" + "react": "^19.2.1" } }, "node_modules/react-is": { @@ -12470,9 +12484,9 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", - "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -13186,9 +13200,9 @@ "license": "ISC" }, "node_modules/shadcn": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.5.0.tgz", - "integrity": "sha512-5f9tn7gHOiI8CqJ8LGrUEmX6dOivGgSbHkMCeOMCzxHZy9cPwyuRXt7ZyjwukO8QH9kGqi6oiKOpfvTXNGBP4g==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.5.1.tgz", + "integrity": "sha512-yLbIDouYv8Xz25BxV/GAGC/46R7/oNwoXIs/IFIYXK47+fKcFIYzThtBqJwFEZTzkkvqJCo+MBg0K9QLTmhFmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14226,12 +14240,12 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "license": "MIT", "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -14258,9 +14272,9 @@ } }, "node_modules/type-fest": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.2.0.tgz", - "integrity": "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.0.tgz", + "integrity": "sha512-d9CwU93nN0IA1QL+GSNDdwLAu1Ew5ZjTwupvedwg3WdfoH6pIDvYQ2hV0Uc2nKBLPq7NB5apCx57MLS5qlmO5g==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { @@ -14408,16 +14422,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.47.0.tgz", - "integrity": "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz", + "integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.47.0", - "@typescript-eslint/parser": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/utils": "8.47.0" + "@typescript-eslint/eslint-plugin": "8.48.1", + "@typescript-eslint/parser": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -14451,31 +14465,31 @@ } }, "node_modules/unconfig": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.4.1.tgz", - "integrity": "sha512-uyQ7LElcGizrOGZyIq9KU+xkuEjcRf9IpmDTkCSYv5mEeZzrXSj6rb51C0L+WTedsmAoVxW9WKrLWhSwebIM9Q==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.4.2.tgz", + "integrity": "sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ==", "dev": true, "license": "MIT", "dependencies": { - "@quansync/fs": "^0.1.5", + "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", - "quansync": "^0.2.11", - "unconfig-core": "7.4.1" + "quansync": "^1.0.0", + "unconfig-core": "7.4.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/unconfig-core": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.4.1.tgz", - "integrity": "sha512-Bp/bPZjV2Vl/fofoA2OYLSnw1Z0MOhCX7zHnVCYrazpfZvseBbGhwcNQMxsg185Mqh7VZQqK3C8hFG/Dyng+yA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.4.2.tgz", + "integrity": "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg==", "dev": true, "license": "MIT", "dependencies": { - "@quansync/fs": "^0.1.5", - "quansync": "^0.2.11" + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -14632,9 +14646,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "funding": [ { "type": "opencollective", @@ -14775,9 +14789,9 @@ } }, "node_modules/vite": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", - "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14850,17 +14864,17 @@ } }, "node_modules/vite-plugin-pwa": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.1.0.tgz", - "integrity": "sha512-VsSpdubPzXhHWVINcSx6uHRMpOHVHQcHsef1QgkOlEoaIDAlssFEW88LBq1a59BuokAhsh2kUDJbaX1bZv4Bjw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz", + "integrity": "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", - "workbox-build": "^7.3.0", - "workbox-window": "^7.3.0" + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" }, "engines": { "node": ">=16.0.0" @@ -14871,8 +14885,8 @@ "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "workbox-build": "^7.3.0", - "workbox-window": "^7.3.0" + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" }, "peerDependenciesMeta": { "@vite-pwa/assets-generator": { @@ -14880,6 +14894,490 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 43e9023..3ef6b9e 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -4,7 +4,7 @@ import type { Transaction, AnalyticsTransaction, Balance, - ApiResponse, + PaginatedResponse, NotificationSettings, NotificationTest, NotificationService, @@ -36,14 +36,14 @@ const api = axios.create({ export const apiClient = { // Get all accounts getAccounts: async (): Promise => { - const response = await api.get>("/accounts"); - return response.data.data; + const response = await api.get("/accounts"); + return response.data; }, // Get account by ID getAccount: async (id: string): Promise => { - const response = await api.get>(`/accounts/${id}`); - return response.data.data; + const response = await api.get(`/accounts/${id}`); + return response.data; }, // Update account details @@ -51,16 +51,17 @@ export const apiClient = { id: string, updates: AccountUpdate, ): Promise<{ id: string; display_name?: string }> => { - const response = await api.put< - ApiResponse<{ id: string; display_name?: string }> - >(`/accounts/${id}`, updates); - return response.data.data; + const response = await api.put<{ id: string; display_name?: string }>( + `/accounts/${id}`, + updates, + ); + return response.data; }, // Get all balances getBalances: async (): Promise => { - const response = await api.get>("/balances"); - return response.data.data; + const response = await api.get("/balances"); + return response.data; }, // Get historical balances for balance progression chart @@ -72,18 +73,18 @@ export const apiClient = { if (days) queryParams.append("days", days.toString()); if (accountId) queryParams.append("account_id", accountId); - const response = await api.get>( + const response = await api.get( `/balances/history?${queryParams.toString()}`, ); - return response.data.data; + return response.data; }, // Get balances for specific account getAccountBalances: async (accountId: string): Promise => { - const response = await api.get>( + const response = await api.get( `/accounts/${accountId}/balances`, ); - return response.data.data; + return response.data; }, // Get transactions with optional filters @@ -97,7 +98,7 @@ export const apiClient = { summaryOnly?: boolean; minAmount?: number; maxAmount?: number; - }): Promise> => { + }): Promise> => { const queryParams = new URLSearchParams(); if (params?.accountId) queryParams.append("account_id", params.accountId); @@ -117,7 +118,7 @@ export const apiClient = { queryParams.append("max_amount", params.maxAmount.toString()); } - const response = await api.get>( + const response = await api.get>( `/transactions?${queryParams.toString()}`, ); return response.data; @@ -125,29 +126,27 @@ export const apiClient = { // Get transaction by ID getTransaction: async (id: string): Promise => { - const response = await api.get>( - `/transactions/${id}`, - ); - return response.data.data; + const response = await api.get(`/transactions/${id}`); + return response.data; }, // Get notification settings getNotificationSettings: async (): Promise => { - const response = await api.get>( + const response = await api.get( "/notifications/settings", ); - return response.data.data; + return response.data; }, // Update notification settings updateNotificationSettings: async ( settings: NotificationSettings, ): Promise => { - const response = await api.put>( + const response = await api.put( "/notifications/settings", settings, ); - return response.data.data; + return response.data; }, // Test notification @@ -157,11 +156,11 @@ export const apiClient = { // Get notification services getNotificationServices: async (): Promise => { - const response = await api.get>( + const response = await api.get( "/notifications/services", ); // Convert object to array format - const servicesData = response.data.data; + const servicesData = response.data; return Object.values(servicesData); }, @@ -172,8 +171,8 @@ export const apiClient = { // Health check getHealth: async (): Promise => { - const response = await api.get>("/health"); - return response.data.data; + const response = await api.get("/health"); + return response.data; }, // Analytics endpoints @@ -181,10 +180,10 @@ export const apiClient = { const queryParams = new URLSearchParams(); if (days) queryParams.append("days", days.toString()); - const response = await api.get>( + const response = await api.get( `/transactions/stats?${queryParams.toString()}`, ); - return response.data.data; + return response.data; }, // Get all transactions for analytics (no pagination) @@ -194,10 +193,10 @@ export const apiClient = { const queryParams = new URLSearchParams(); if (days) queryParams.append("days", days.toString()); - const response = await api.get>( + const response = await api.get( `/transactions/analytics?${queryParams.toString()}`, ); - return response.data.data; + return response.data; }, // Get monthly transaction statistics (pre-calculated) @@ -215,16 +214,14 @@ export const apiClient = { if (days) queryParams.append("days", days.toString()); const response = await api.get< - ApiResponse< - Array<{ - month: string; - income: number; - expenses: number; - net: number; - }> - > + Array<{ + month: string; + income: number; + expenses: number; + net: number; + }> >(`/transactions/monthly-stats?${queryParams.toString()}`); - return response.data.data; + return response.data; }, // Get sync operations history @@ -232,24 +229,23 @@ export const apiClient = { limit: number = 50, offset: number = 0, ): Promise => { - const response = await api.get>( + const response = await api.get( `/sync/operations?limit=${limit}&offset=${offset}`, ); - return response.data.data; + return response.data; }, // Bank management endpoints getBankInstitutions: async (country: string): Promise => { - const response = await api.get>( + const response = await api.get( `/banks/institutions?country=${country}`, ); - return response.data.data; + return response.data; }, getBankConnectionsStatus: async (): Promise => { - const response = - await api.get>("/banks/status"); - return response.data.data; + const response = await api.get("/banks/status"); + return response.data; }, createBankConnection: async ( @@ -260,14 +256,11 @@ export const apiClient = { const finalRedirectUrl = redirectUrl || `${window.location.origin}/bank-connected`; - const response = await api.post>( - "/banks/connect", - { - institution_id: institutionId, - redirect_url: finalRedirectUrl, - }, - ); - return response.data.data; + const response = await api.post("/banks/connect", { + institution_id: institutionId, + redirect_url: finalRedirectUrl, + }); + return response.data; }, deleteBankConnection: async (requisitionId: string): Promise => { @@ -275,39 +268,44 @@ export const apiClient = { }, getSupportedCountries: async (): Promise => { - const response = await api.get>("/banks/countries"); - return response.data.data; + const response = await api.get("/banks/countries"); + return response.data; }, // Backup endpoints getBackupSettings: async (): Promise => { - const response = - await api.get>("/backup/settings"); - return response.data.data; + const response = await api.get("/backup/settings"); + return response.data; }, updateBackupSettings: async ( settings: BackupSettings, ): Promise => { - const response = await api.put>( + const response = await api.put( "/backup/settings", settings, ); - return response.data.data; + return response.data; }, - testBackupConnection: async (test: BackupTest): Promise> => { - const response = await api.post>("/backup/test", test); + testBackupConnection: async (test: BackupTest): Promise<{ connected?: boolean }> => { + const response = await api.post<{ connected?: boolean }>( + "/backup/test", + test, + ); return response.data; }, listBackups: async (): Promise => { - const response = await api.get>("/backup/list"); - return response.data.data; + const response = await api.get("/backup/list"); + return response.data; }, - performBackupOperation: async (operation: BackupOperation): Promise> => { - const response = await api.post>("/backup/operation", operation); + performBackupOperation: async (operation: BackupOperation): Promise<{ operation: string; completed: boolean }> => { + const response = await api.post<{ operation: string; completed: boolean }>( + "/backup/operation", + operation, + ); return response.data; }, }; diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index e4ae1d6..b849dab 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -133,26 +133,14 @@ export interface Bank { logo_url?: string; } -export interface ApiResponse { - data: T; - message?: string; - success: boolean; - pagination?: { - total: number; - page: number; - per_page: number; - total_pages: number; - has_next: boolean; - has_prev: boolean; - }; -} - export interface PaginatedResponse { data: T[]; total: number; page: number; per_page: number; total_pages: number; + has_next: boolean; + has_prev: boolean; } // Notification types diff --git a/leggen/api/models/common.py b/leggen/api/models/common.py index c2a056d..7f02ca1 100644 --- a/leggen/api/models/common.py +++ b/leggen/api/models/common.py @@ -1,29 +1,17 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict, Generic, List, TypeVar from pydantic import BaseModel - -class APIResponse(BaseModel): - """Base API response model""" - - success: bool = True - message: Optional[str] = None - data: Optional[Any] = None +T = TypeVar("T") -class ErrorResponse(BaseModel): - """Error response model""" - - success: bool = False - message: str - error_code: Optional[str] = None - details: Optional[Dict[str, Any]] = None - - -class PaginatedResponse(BaseModel): +class PaginatedResponse(BaseModel, Generic[T]): """Paginated response model""" - success: bool = True - data: list - pagination: Dict[str, Any] - message: Optional[str] = None + data: List[T] + total: int + page: int + per_page: int + total_pages: int + has_next: bool + has_prev: bool diff --git a/leggen/api/routes/accounts.py b/leggen/api/routes/accounts.py index 2f79bcd..e53177e 100644 --- a/leggen/api/routes/accounts.py +++ b/leggen/api/routes/accounts.py @@ -10,15 +10,14 @@ from leggen.api.models.accounts import ( Transaction, TransactionSummary, ) -from leggen.api.models.common import APIResponse from leggen.services.database_service import DatabaseService router = APIRouter() database_service = DatabaseService() -@router.get("/accounts", response_model=APIResponse) -async def get_all_accounts() -> APIResponse: +@router.get("/accounts") +async def get_all_accounts() -> List[AccountDetails]: """Get all connected accounts from database""" try: accounts = [] @@ -68,11 +67,7 @@ async def get_all_accounts() -> APIResponse: ) continue - return APIResponse( - success=True, - data=accounts, - message=f"Retrieved {len(accounts)} accounts from database", - ) + return accounts except Exception as e: logger.error(f"Failed to get accounts: {e}") @@ -81,8 +76,8 @@ async def get_all_accounts() -> APIResponse: ) from e -@router.get("/accounts/{account_id}", response_model=APIResponse) -async def get_account_details(account_id: str) -> APIResponse: +@router.get("/accounts/{account_id}") +async def get_account_details(account_id: str) -> AccountDetails: """Get details for a specific account from database""" try: # Get account details from database @@ -122,11 +117,7 @@ async def get_account_details(account_id: str) -> APIResponse: balances=balances, ) - return APIResponse( - success=True, - data=account, - message=f"Account details retrieved from database for {account_id}", - ) + return account except HTTPException: raise @@ -137,8 +128,8 @@ async def get_account_details(account_id: str) -> APIResponse: ) from e -@router.get("/accounts/{account_id}/balances", response_model=APIResponse) -async def get_account_balances(account_id: str) -> APIResponse: +@router.get("/accounts/{account_id}/balances") +async def get_account_balances(account_id: str) -> List[AccountBalance]: """Get balances for a specific account from database""" try: # Get balances from database instead of GoCardless API @@ -155,11 +146,7 @@ async def get_account_balances(account_id: str) -> APIResponse: ) ) - return APIResponse( - success=True, - data=balances, - message=f"Retrieved {len(balances)} balances for account {account_id}", - ) + return balances except Exception as e: logger.error( @@ -170,8 +157,8 @@ async def get_account_balances(account_id: str) -> APIResponse: ) from e -@router.get("/balances", response_model=APIResponse) -async def get_all_balances() -> APIResponse: +@router.get("/balances") +async def get_all_balances() -> List[dict]: """Get all balances from all accounts in database""" try: # Get all accounts first to iterate through them @@ -207,11 +194,7 @@ async def get_all_balances() -> APIResponse: ) continue - return APIResponse( - success=True, - data=all_balances, - message=f"Retrieved {len(all_balances)} balances from {len(db_accounts)} accounts", - ) + return all_balances except Exception as e: logger.error(f"Failed to get all balances: {e}") @@ -220,7 +203,7 @@ async def get_all_balances() -> APIResponse: ) from e -@router.get("/balances/history", response_model=APIResponse) +@router.get("/balances/history") async def get_historical_balances( days: Optional[int] = Query( default=365, le=1095, ge=1, description="Number of days of history to retrieve" @@ -228,7 +211,7 @@ async def get_historical_balances( account_id: Optional[str] = Query( default=None, description="Filter by specific account ID" ), -) -> APIResponse: +) -> List[dict]: """Get historical balance progression calculated from transaction history""" try: # Get historical balances from database @@ -236,11 +219,7 @@ async def get_historical_balances( account_id=account_id, days=days or 365 ) - return APIResponse( - success=True, - data=historical_balances, - message=f"Retrieved {len(historical_balances)} historical balance points over {days} days", - ) + return historical_balances except Exception as e: logger.error(f"Failed to get historical balances: {e}") @@ -249,7 +228,7 @@ async def get_historical_balances( ) from e -@router.get("/accounts/{account_id}/transactions", response_model=APIResponse) +@router.get("/accounts/{account_id}/transactions") async def get_account_transactions( account_id: str, limit: Optional[int] = Query(default=100, le=500), @@ -257,7 +236,7 @@ async def get_account_transactions( summary_only: bool = Query( default=False, description="Return transaction summaries only" ), -) -> APIResponse: +) -> Union[List[TransactionSummary], List[Transaction]]: """Get transactions for a specific account from database""" try: # Get transactions from database instead of GoCardless API @@ -308,12 +287,7 @@ async def get_account_transactions( for txn in db_transactions ] - actual_offset = offset or 0 - return APIResponse( - success=True, - data=data, - message=f"Retrieved {len(data)} transactions (showing {actual_offset + 1}-{actual_offset + len(data)} of {total_transactions})", - ) + return data except Exception as e: logger.error( @@ -324,10 +298,10 @@ async def get_account_transactions( ) from e -@router.put("/accounts/{account_id}", response_model=APIResponse) +@router.put("/accounts/{account_id}") async def update_account_details( account_id: str, update_data: AccountUpdate -) -> APIResponse: +) -> dict: """Update account details (currently only display_name)""" try: # Get current account details @@ -346,11 +320,7 @@ async def update_account_details( # Persist updated account details await database_service.persist_account_details(updated_account_data) - return APIResponse( - success=True, - data={"id": account_id, "display_name": update_data.display_name}, - message=f"Account {account_id} display name updated successfully", - ) + return {"id": account_id, "display_name": update_data.display_name} except HTTPException: raise diff --git a/leggen/api/routes/backup.py b/leggen/api/routes/backup.py index 973fde1..de14e30 100644 --- a/leggen/api/routes/backup.py +++ b/leggen/api/routes/backup.py @@ -9,7 +9,6 @@ from leggen.api.models.backup import ( BackupTest, S3Config, ) -from leggen.api.models.common import APIResponse from leggen.models.config import S3BackupConfig from leggen.services.backup_service import BackupService from leggen.utils.config import config @@ -18,8 +17,8 @@ from leggen.utils.paths import path_manager router = APIRouter() -@router.get("/backup/settings", response_model=APIResponse) -async def get_backup_settings() -> APIResponse: +@router.get("/backup/settings") +async def get_backup_settings() -> BackupSettings: """Get current backup settings.""" try: backup_config = config.backup_config @@ -41,11 +40,7 @@ async def get_backup_settings() -> APIResponse: else None, ) - return APIResponse( - success=True, - data=settings, - message="Backup settings retrieved successfully", - ) + return settings except Exception as e: logger.error(f"Failed to get backup settings: {e}") @@ -54,8 +49,8 @@ async def get_backup_settings() -> APIResponse: ) from e -@router.put("/backup/settings", response_model=APIResponse) -async def update_backup_settings(settings: BackupSettings) -> APIResponse: +@router.put("/backup/settings") +async def update_backup_settings(settings: BackupSettings) -> dict: """Update backup settings.""" try: # First test the connection if S3 config is provided @@ -99,11 +94,7 @@ async def update_backup_settings(settings: BackupSettings) -> APIResponse: if backup_config: config.update_section("backup", backup_config) - return APIResponse( - success=True, - data={"updated": True}, - message="Backup settings updated successfully", - ) + return {"updated": True} except HTTPException: raise @@ -114,8 +105,8 @@ async def update_backup_settings(settings: BackupSettings) -> APIResponse: ) from e -@router.post("/backup/test", response_model=APIResponse) -async def test_backup_connection(test_request: BackupTest) -> APIResponse: +@router.post("/backup/test") +async def test_backup_connection(test_request: BackupTest) -> dict: """Test backup connection.""" try: if test_request.service != "s3": @@ -137,18 +128,13 @@ async def test_backup_connection(test_request: BackupTest) -> APIResponse: backup_service = BackupService() success = await backup_service.test_connection(s3_config) - if success: - return APIResponse( - success=True, - data={"connected": True}, - message="S3 connection test successful", - ) - else: - return APIResponse( - success=False, - message="S3 connection test failed", + if not success: + raise HTTPException( + status_code=400, detail="S3 connection test failed" ) + return {"connected": True} + except HTTPException: raise except Exception as e: @@ -158,18 +144,14 @@ async def test_backup_connection(test_request: BackupTest) -> APIResponse: ) from e -@router.get("/backup/list", response_model=APIResponse) -async def list_backups() -> APIResponse: +@router.get("/backup/list") +async def list_backups() -> list: """List available backups.""" try: backup_config = config.backup_config.get("s3", {}) if not backup_config.get("bucket_name"): - return APIResponse( - success=True, - data=[], - message="No S3 backup configuration found", - ) + return [] # Convert config to model s3_config = S3BackupConfig(**backup_config) @@ -177,11 +159,7 @@ async def list_backups() -> APIResponse: backups = await backup_service.list_backups() - return APIResponse( - success=True, - data=backups, - message=f"Found {len(backups)} backups", - ) + return backups except Exception as e: logger.error(f"Failed to list backups: {e}") @@ -190,8 +168,8 @@ async def list_backups() -> APIResponse: ) from e -@router.post("/backup/operation", response_model=APIResponse) -async def backup_operation(operation_request: BackupOperation) -> APIResponse: +@router.post("/backup/operation") +async def backup_operation(operation_request: BackupOperation) -> dict: """Perform backup operation (backup or restore).""" try: backup_config = config.backup_config.get("s3", {}) @@ -214,18 +192,13 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse: database_path = path_manager.get_database_path() success = await backup_service.backup_database(database_path) - if success: - return APIResponse( - success=True, - data={"operation": "backup", "completed": True}, - message="Database backup completed successfully", - ) - else: - return APIResponse( - success=False, - message="Database backup failed", + if not success: + raise HTTPException( + status_code=500, detail="Database backup failed" ) + return {"operation": "backup", "completed": True} + elif operation_request.operation == "restore": if not operation_request.backup_key: raise HTTPException( @@ -239,17 +212,12 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse: operation_request.backup_key, database_path ) - if success: - return APIResponse( - success=True, - data={"operation": "restore", "completed": True}, - message="Database restore completed successfully", - ) - else: - return APIResponse( - success=False, - message="Database restore failed", + if not success: + raise HTTPException( + status_code=500, detail="Database restore failed" ) + + return {"operation": "restore", "completed": True} else: raise HTTPException( status_code=400, detail="Invalid operation. Use 'backup' or 'restore'" diff --git a/leggen/api/routes/banks.py b/leggen/api/routes/banks.py index dee9212..f5746c2 100644 --- a/leggen/api/routes/banks.py +++ b/leggen/api/routes/banks.py @@ -8,7 +8,6 @@ from leggen.api.models.banks import ( BankInstitution, BankRequisition, ) -from leggen.api.models.common import APIResponse from leggen.services.gocardless_service import GoCardlessService from leggen.utils.gocardless import REQUISITION_STATUS @@ -16,10 +15,10 @@ router = APIRouter() gocardless_service = GoCardlessService() -@router.get("/banks/institutions", response_model=APIResponse) +@router.get("/banks/institutions") async def get_bank_institutions( country: str = Query(default="PT", description="Country code (e.g., PT, ES, FR)"), -) -> APIResponse: +) -> list[BankInstitution]: """Get available bank institutions for a country""" try: institutions_response = await gocardless_service.get_institutions(country) @@ -41,11 +40,7 @@ async def get_bank_institutions( for inst in institutions_data ] - return APIResponse( - success=True, - data=institutions, - message=f"Found {len(institutions)} institutions for {country}", - ) + return institutions except Exception as e: logger.error(f"Failed to get institutions for {country}: {e}") @@ -54,8 +49,8 @@ async def get_bank_institutions( ) from e -@router.post("/banks/connect", response_model=APIResponse) -async def connect_to_bank(request: BankConnectionRequest) -> APIResponse: +@router.post("/banks/connect") +async def connect_to_bank(request: BankConnectionRequest) -> BankRequisition: """Create a connection to a bank (requisition)""" try: redirect_url = request.redirect_url or "http://localhost:8000/" @@ -72,11 +67,7 @@ async def connect_to_bank(request: BankConnectionRequest) -> APIResponse: accounts=requisition_data.get("accounts", []), ) - return APIResponse( - success=True, - data=requisition, - message="Bank connection created. Please visit the link to authorize.", - ) + return requisition except Exception as e: logger.error(f"Failed to connect to bank {request.institution_id}: {e}") @@ -85,8 +76,8 @@ async def connect_to_bank(request: BankConnectionRequest) -> APIResponse: ) from e -@router.get("/banks/status", response_model=APIResponse) -async def get_bank_connections_status() -> APIResponse: +@router.get("/banks/status") +async def get_bank_connections_status() -> list[BankConnectionStatus]: """Get status of all bank connections""" try: requisitions_data = await gocardless_service.get_requisitions() @@ -110,11 +101,7 @@ async def get_bank_connections_status() -> APIResponse: ) ) - return APIResponse( - success=True, - data=connections, - message=f"Found {len(connections)} bank connections", - ) + return connections except Exception as e: logger.error(f"Failed to get bank connection status: {e}") @@ -123,8 +110,8 @@ async def get_bank_connections_status() -> APIResponse: ) from e -@router.delete("/banks/connections/{requisition_id}", response_model=APIResponse) -async def delete_bank_connection(requisition_id: str) -> APIResponse: +@router.delete("/banks/connections/{requisition_id}") +async def delete_bank_connection(requisition_id: str) -> dict: """Delete a bank connection""" try: # Delete the requisition from GoCardless @@ -134,10 +121,7 @@ async def delete_bank_connection(requisition_id: str) -> APIResponse: # We should check if the operation was actually successful logger.info(f"GoCardless delete response for {requisition_id}: {result}") - return APIResponse( - success=True, - message=f"Bank connection {requisition_id} deleted successfully", - ) + return {"deleted": requisition_id} except httpx.HTTPStatusError as http_err: logger.error( @@ -164,8 +148,8 @@ async def delete_bank_connection(requisition_id: str) -> APIResponse: ) from e -@router.get("/banks/countries", response_model=APIResponse) -async def get_supported_countries() -> APIResponse: +@router.get("/banks/countries") +async def get_supported_countries() -> list[dict]: """Get list of supported countries""" countries = [ {"code": "AT", "name": "Austria"}, @@ -201,8 +185,4 @@ async def get_supported_countries() -> APIResponse: {"code": "GB", "name": "United Kingdom"}, ] - return APIResponse( - success=True, - data=countries, - message="Supported countries retrieved successfully", - ) + return countries diff --git a/leggen/api/routes/notifications.py b/leggen/api/routes/notifications.py index 3b23d23..aef8768 100644 --- a/leggen/api/routes/notifications.py +++ b/leggen/api/routes/notifications.py @@ -3,7 +3,6 @@ from typing import Any, Dict from fastapi import APIRouter, HTTPException from loguru import logger -from leggen.api.models.common import APIResponse from leggen.api.models.notifications import ( DiscordConfig, NotificationFilters, @@ -18,8 +17,8 @@ router = APIRouter() notification_service = NotificationService() -@router.get("/notifications/settings", response_model=APIResponse) -async def get_notification_settings() -> APIResponse: +@router.get("/notifications/settings") +async def get_notification_settings() -> NotificationSettings: """Get current notification settings""" try: notifications_config = config.notifications_config @@ -49,11 +48,7 @@ async def get_notification_settings() -> APIResponse: ), ) - return APIResponse( - success=True, - data=settings, - message="Notification settings retrieved successfully", - ) + return settings except Exception as e: logger.error(f"Failed to get notification settings: {e}") @@ -62,8 +57,8 @@ async def get_notification_settings() -> APIResponse: ) from e -@router.put("/notifications/settings", response_model=APIResponse) -async def update_notification_settings(settings: NotificationSettings) -> APIResponse: +@router.put("/notifications/settings") +async def update_notification_settings(settings: NotificationSettings) -> dict: """Update notification settings""" try: # Update notifications config @@ -95,11 +90,7 @@ async def update_notification_settings(settings: NotificationSettings) -> APIRes if filters_config: config.update_section("filters", filters_config) - return APIResponse( - success=True, - data={"updated": True}, - message="Notification settings updated successfully", - ) + return {"updated": True} except Exception as e: logger.error(f"Failed to update notification settings: {e}") @@ -108,26 +99,24 @@ async def update_notification_settings(settings: NotificationSettings) -> APIRes ) from e -@router.post("/notifications/test", response_model=APIResponse) -async def test_notification(test_request: NotificationTest) -> APIResponse: +@router.post("/notifications/test") +async def test_notification(test_request: NotificationTest) -> dict: """Send a test notification""" try: success = await notification_service.send_test_notification( test_request.service, test_request.message ) - if success: - return APIResponse( - success=True, - data={"sent": True}, - message=f"Test notification sent to {test_request.service} successfully", - ) - else: - return APIResponse( - success=False, - message=f"Failed to send test notification to {test_request.service}", + if not success: + raise HTTPException( + status_code=400, + detail=f"Failed to send test notification to {test_request.service}", ) + return {"sent": True} + + except HTTPException: + raise except Exception as e: logger.error(f"Failed to send test notification: {e}") raise HTTPException( @@ -135,8 +124,8 @@ async def test_notification(test_request: NotificationTest) -> APIResponse: ) from e -@router.get("/notifications/services", response_model=APIResponse) -async def get_notification_services() -> APIResponse: +@router.get("/notifications/services") +async def get_notification_services() -> dict: """Get available notification services and their status""" try: notifications_config = config.notifications_config @@ -164,11 +153,7 @@ async def get_notification_services() -> APIResponse: }, } - return APIResponse( - success=True, - data=services, - message="Notification services status retrieved successfully", - ) + return services except Exception as e: logger.error(f"Failed to get notification services: {e}") @@ -177,8 +162,8 @@ async def get_notification_services() -> APIResponse: ) from e -@router.delete("/notifications/settings/{service}", response_model=APIResponse) -async def delete_notification_service(service: str) -> APIResponse: +@router.delete("/notifications/settings/{service}") +async def delete_notification_service(service: str) -> dict: """Delete/disable a notification service""" try: if service not in ["discord", "telegram"]: @@ -191,12 +176,10 @@ async def delete_notification_service(service: str) -> APIResponse: del notifications_config[service] config.update_section("notifications", notifications_config) - return APIResponse( - success=True, - data={"deleted": service}, - message=f"{service.capitalize()} notification service deleted successfully", - ) + return {"deleted": service} + except HTTPException: + raise except Exception as e: logger.error(f"Failed to delete notification service {service}: {e}") raise HTTPException( diff --git a/leggen/api/routes/sync.py b/leggen/api/routes/sync.py index 69bef0d..92f5f00 100644 --- a/leggen/api/routes/sync.py +++ b/leggen/api/routes/sync.py @@ -3,7 +3,6 @@ from typing import Optional from fastapi import APIRouter, BackgroundTasks, HTTPException from loguru import logger -from leggen.api.models.common import APIResponse from leggen.api.models.sync import SchedulerConfig, SyncRequest from leggen.background.scheduler import scheduler from leggen.services.sync_service import SyncService @@ -13,8 +12,8 @@ router = APIRouter() sync_service = SyncService() -@router.get("/sync/status", response_model=APIResponse) -async def get_sync_status() -> APIResponse: +@router.get("/sync/status") +async def get_sync_status() -> dict: """Get current sync status""" try: status = await sync_service.get_sync_status() @@ -24,9 +23,7 @@ async def get_sync_status() -> APIResponse: if next_sync_time: status.next_sync = next_sync_time - return APIResponse( - success=True, data=status, message="Sync status retrieved successfully" - ) + return status except Exception as e: logger.error(f"Failed to get sync status: {e}") @@ -35,18 +32,18 @@ async def get_sync_status() -> APIResponse: ) from e -@router.post("/sync", response_model=APIResponse) +@router.post("/sync") async def trigger_sync( background_tasks: BackgroundTasks, sync_request: Optional[SyncRequest] = None -) -> APIResponse: +) -> dict: """Trigger a manual sync operation""" try: # Check if sync is already running status = await sync_service.get_sync_status() if status.is_running and not (sync_request and sync_request.force): - return APIResponse( - success=False, - message="Sync is already running. Use 'force: true' to override.", + raise HTTPException( + status_code=409, + detail="Sync is already running. Use 'force: true' to override.", ) # Determine what to sync @@ -58,9 +55,6 @@ async def trigger_sync( sync_request.force if sync_request else False, "api", # trigger_type ) - message = ( - f"Started sync for {len(sync_request.account_ids)} specific accounts" - ) else: # Sync all accounts in background background_tasks.add_task( @@ -68,17 +62,14 @@ async def trigger_sync( sync_request.force if sync_request else False, "api", # trigger_type ) - message = "Started sync for all accounts" - return APIResponse( - success=True, - data={ - "sync_started": True, - "force": sync_request.force if sync_request else False, - }, - message=message, - ) + return { + "sync_started": True, + "force": sync_request.force if sync_request else False, + } + except HTTPException: + raise except Exception as e: logger.error(f"Failed to trigger sync: {e}") raise HTTPException( @@ -86,8 +77,8 @@ async def trigger_sync( ) from e -@router.post("/sync/now", response_model=APIResponse) -async def sync_now(sync_request: Optional[SyncRequest] = None) -> APIResponse: +@router.post("/sync/now") +async def sync_now(sync_request: Optional[SyncRequest] = None) -> dict: """Run sync synchronously and return results (slower, for testing)""" try: if sync_request and sync_request.account_ids: @@ -99,13 +90,7 @@ async def sync_now(sync_request: Optional[SyncRequest] = None) -> APIResponse: sync_request.force if sync_request else False, "api" ) - return APIResponse( - success=result.success, - data=result, - message="Sync completed" - if result.success - else f"Sync failed with {len(result.errors)} errors", - ) + return result except Exception as e: logger.error(f"Failed to run sync: {e}") @@ -114,8 +99,8 @@ async def sync_now(sync_request: Optional[SyncRequest] = None) -> APIResponse: ) from e -@router.get("/sync/scheduler", response_model=APIResponse) -async def get_scheduler_config() -> APIResponse: +@router.get("/sync/scheduler") +async def get_scheduler_config() -> dict: """Get current scheduler configuration""" try: scheduler_config = config.scheduler_config @@ -131,11 +116,7 @@ async def get_scheduler_config() -> APIResponse: else False, } - return APIResponse( - success=True, - data=response_data, - message="Scheduler configuration retrieved successfully", - ) + return response_data except Exception as e: logger.error(f"Failed to get scheduler config: {e}") @@ -144,8 +125,8 @@ async def get_scheduler_config() -> APIResponse: ) from e -@router.put("/sync/scheduler", response_model=APIResponse) -async def update_scheduler_config(scheduler_config: SchedulerConfig) -> APIResponse: +@router.put("/sync/scheduler") +async def update_scheduler_config(scheduler_config: SchedulerConfig) -> dict: """Update scheduler configuration""" try: # Validate cron expression if provided @@ -168,12 +149,10 @@ async def update_scheduler_config(scheduler_config: SchedulerConfig) -> APIRespo # Reschedule the job scheduler.reschedule_sync(schedule_data) - return APIResponse( - success=True, - data=schedule_data, - message="Scheduler configuration updated successfully", - ) + return schedule_data + except HTTPException: + raise except Exception as e: logger.error(f"Failed to update scheduler config: {e}") raise HTTPException( @@ -181,15 +160,15 @@ async def update_scheduler_config(scheduler_config: SchedulerConfig) -> APIRespo ) from e -@router.post("/sync/scheduler/start", response_model=APIResponse) -async def start_scheduler() -> APIResponse: +@router.post("/sync/scheduler/start") +async def start_scheduler() -> dict: """Start the background scheduler""" try: if not scheduler.scheduler.running: scheduler.start() - return APIResponse(success=True, message="Scheduler started successfully") + return {"started": True} else: - return APIResponse(success=True, message="Scheduler is already running") + return {"started": False, "message": "Scheduler is already running"} except Exception as e: logger.error(f"Failed to start scheduler: {e}") @@ -198,15 +177,15 @@ async def start_scheduler() -> APIResponse: ) from e -@router.post("/sync/scheduler/stop", response_model=APIResponse) -async def stop_scheduler() -> APIResponse: +@router.post("/sync/scheduler/stop") +async def stop_scheduler() -> dict: """Stop the background scheduler""" try: if scheduler.scheduler.running: scheduler.shutdown() - return APIResponse(success=True, message="Scheduler stopped successfully") + return {"stopped": True} else: - return APIResponse(success=True, message="Scheduler is already stopped") + return {"stopped": False, "message": "Scheduler is already stopped"} except Exception as e: logger.error(f"Failed to stop scheduler: {e}") @@ -215,19 +194,15 @@ async def stop_scheduler() -> APIResponse: ) from e -@router.get("/sync/operations", response_model=APIResponse) -async def get_sync_operations(limit: int = 50, offset: int = 0) -> APIResponse: +@router.get("/sync/operations") +async def get_sync_operations(limit: int = 50, offset: int = 0) -> dict: """Get sync operations history""" try: operations = await sync_service.database.get_sync_operations( limit=limit, offset=offset ) - return APIResponse( - success=True, - data={"operations": operations, "count": len(operations)}, - message="Sync operations retrieved successfully", - ) + return {"operations": operations, "count": len(operations)} except Exception as e: logger.error(f"Failed to get sync operations: {e}") diff --git a/leggen/api/routes/transactions.py b/leggen/api/routes/transactions.py index 3c02c0e..4a1c1d7 100644 --- a/leggen/api/routes/transactions.py +++ b/leggen/api/routes/transactions.py @@ -5,14 +5,14 @@ from fastapi import APIRouter, HTTPException, Query from loguru import logger from leggen.api.models.accounts import Transaction, TransactionSummary -from leggen.api.models.common import APIResponse, PaginatedResponse +from leggen.api.models.common import PaginatedResponse from leggen.services.database_service import DatabaseService router = APIRouter() database_service = DatabaseService() -@router.get("/transactions", response_model=PaginatedResponse) +@router.get("/transactions") async def get_all_transactions( page: int = Query(default=1, ge=1, description="Page number (1-based)"), per_page: int = Query(default=50, le=500, description="Items per page"), @@ -35,7 +35,7 @@ async def get_all_transactions( default=None, description="Search in transaction descriptions" ), account_id: Optional[str] = Query(default=None, description="Filter by account ID"), -) -> PaginatedResponse: +) -> PaginatedResponse[Union[TransactionSummary, Transaction]]: """Get all transactions from database with filtering options""" try: # Calculate offset from page and per_page @@ -103,16 +103,13 @@ async def get_all_transactions( total_pages = (total_transactions + per_page - 1) // per_page return PaginatedResponse( - success=True, data=data, - pagination={ - "total": total_transactions, - "page": page, - "per_page": per_page, - "total_pages": total_pages, - "has_next": page < total_pages, - "has_prev": page > 1, - }, + total=total_transactions, + page=page, + per_page=per_page, + total_pages=total_pages, + has_next=page < total_pages, + has_prev=page > 1, ) except Exception as e: @@ -122,11 +119,11 @@ async def get_all_transactions( ) from e -@router.get("/transactions/stats", response_model=APIResponse) +@router.get("/transactions/stats") async def get_transaction_stats( days: int = Query(default=30, description="Number of days to include in stats"), account_id: Optional[str] = Query(default=None, description="Filter by account ID"), -) -> APIResponse: +) -> dict: """Get transaction statistics for the last N days from database""" try: # Date range for stats @@ -192,11 +189,7 @@ async def get_transaction_stats( "accounts_included": unique_accounts, } - return APIResponse( - success=True, - data=stats, - message=f"Transaction statistics for last {days} days", - ) + return stats except Exception as e: logger.error(f"Failed to get transaction stats from database: {e}") @@ -205,11 +198,11 @@ async def get_transaction_stats( ) from e -@router.get("/transactions/analytics", response_model=APIResponse) +@router.get("/transactions/analytics") async def get_transactions_for_analytics( days: int = Query(default=365, description="Number of days to include"), account_id: Optional[str] = Query(default=None, description="Filter by account ID"), -) -> APIResponse: +) -> List[dict]: """Get all transactions for analytics (no pagination) for the last N days""" try: # Date range for analytics @@ -242,11 +235,7 @@ async def get_transactions_for_analytics( for txn in transactions ] - return APIResponse( - success=True, - data=transaction_summaries, - message=f"Retrieved {len(transaction_summaries)} transactions for analytics", - ) + return transaction_summaries except Exception as e: logger.error(f"Failed to get transactions for analytics: {e}") @@ -255,11 +244,11 @@ async def get_transactions_for_analytics( ) from e -@router.get("/transactions/monthly-stats", response_model=APIResponse) +@router.get("/transactions/monthly-stats") async def get_monthly_transaction_stats( days: int = Query(default=365, description="Number of days to include"), account_id: Optional[str] = Query(default=None, description="Filter by account ID"), -) -> APIResponse: +) -> List[dict]: """Get monthly transaction statistics aggregated by the database""" try: # Date range for monthly stats @@ -277,11 +266,7 @@ async def get_monthly_transaction_stats( date_to=date_to, ) - return APIResponse( - success=True, - data=monthly_stats, - message=f"Retrieved monthly stats for last {days} days", - ) + return monthly_stats except Exception as e: logger.error(f"Failed to get monthly transaction stats: {e}") diff --git a/leggen/commands/server.py b/leggen/commands/server.py index 9311fec..689e8a4 100644 --- a/leggen/commands/server.py +++ b/leggen/commands/server.py @@ -89,8 +89,6 @@ def create_app() -> FastAPI: async def health(): """Health check endpoint for API connectivity""" try: - from leggen.api.models.common import APIResponse - config_loaded = config._config is not None # Get version dynamically @@ -99,25 +97,17 @@ def create_app() -> FastAPI: except metadata.PackageNotFoundError: version = "dev" - return APIResponse( - success=True, - data={ - "status": "healthy", - "config_loaded": config_loaded, - "version": version, - "message": "API is running and responsive", - }, - message="Health check successful", - ) + return { + "status": "healthy", + "config_loaded": config_loaded, + "version": version, + } except Exception as e: logger.error(f"Health check failed: {e}") - from leggen.api.models.common import APIResponse - - return APIResponse( - success=False, - data={"status": "unhealthy", "error": str(e)}, - message="Health check failed", - ) + return { + "status": "unhealthy", + "error": str(e), + } return app diff --git a/tests/conftest.py b/tests/conftest.py index 2ce67d6..94aaccc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ """Pytest configuration and shared fixtures.""" import json +import os +import shutil import tempfile from pathlib import Path from unittest.mock import patch @@ -8,9 +10,45 @@ from unittest.mock import patch import pytest from fastapi.testclient import TestClient -from leggen.commands.server import create_app from leggen.utils.config import Config +# Create test config before any imports that might load it +_test_config_dir = tempfile.mkdtemp(prefix="leggen_test_") +_test_config_path = Path(_test_config_dir) / "config.toml" + +# Create minimal test config +_config_data = { + "gocardless": { + "key": "test-key", + "secret": "test-secret", + "url": "https://bankaccountdata.gocardless.com/api/v2", + }, + "database": {"sqlite": True}, + "scheduler": {"sync": {"enabled": True, "hour": 3, "minute": 0}}, +} + +import tomli_w +with open(_test_config_path, "wb") as f: + tomli_w.dump(_config_data, f) + +# Set environment variables to point to test config BEFORE importing the app +os.environ["LEGGEN_CONFIG_FILE"] = str(_test_config_path) + +from leggen.commands.server import create_app + + +def pytest_configure(config): + """Pytest hook called before test collection.""" + # Ensure test config is set + os.environ["LEGGEN_CONFIG_FILE"] = str(_test_config_path) + + +def pytest_unconfigure(config): + """Pytest hook called after all tests.""" + # Cleanup test config directory + if Path(_test_config_dir).exists(): + shutil.rmtree(_test_config_dir) + @pytest.fixture def temp_config_dir(): @@ -73,9 +111,12 @@ def mock_auth_token(temp_config_dir): @pytest.fixture -def fastapi_app(): +def fastapi_app(mock_db_path): """Create FastAPI test application.""" - return create_app() + # Patch the database path for the app + with patch("leggen.utils.paths.path_manager.get_database_path", return_value=mock_db_path): + app = create_app() + yield app @pytest.fixture diff --git a/tests/unit/test_analytics_fix.py b/tests/unit/test_analytics_fix.py index b8f7bd0..ad12d34 100644 --- a/tests/unit/test_analytics_fix.py +++ b/tests/unit/test_analytics_fix.py @@ -66,8 +66,7 @@ class TestAnalyticsFix: ) # Verify that the response contains stats for all 600 transactions - assert data["success"] is True - stats = data["data"] + stats = data assert stats["total_transactions"] == 600, ( "Should process all 600 transactions, not just 100" ) @@ -132,8 +131,7 @@ class TestAnalyticsFix: ) # Verify that all 600 transactions are returned - assert data["success"] is True - transactions_data = data["data"] + transactions_data = data assert len(transactions_data) == 600, ( "Analytics endpoint should return all 600 transactions" ) diff --git a/tests/unit/test_api_accounts.py b/tests/unit/test_api_accounts.py index 977cc49..5afc96e 100644 --- a/tests/unit/test_api_accounts.py +++ b/tests/unit/test_api_accounts.py @@ -60,9 +60,8 @@ class TestAccountsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 1 - account = data["data"][0] + assert len(data) == 1 + account = data[0] assert account["id"] == "test-account-123" assert account["institution_id"] == "REVOLUT_REVOLT21" assert len(account["balances"]) == 1 @@ -117,11 +116,9 @@ class TestAccountsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - account = data["data"] - assert account["id"] == "test-account-123" - assert account["iban"] == "LT313250081177977789" - assert len(account["balances"]) == 1 + assert data["id"] == "test-account-123" + assert data["iban"] == "LT313250081177977789" + assert len(data["balances"]) == 1 def test_get_account_balances_success( self, api_client, mock_config, mock_auth_token, mock_db_path @@ -163,11 +160,10 @@ class TestAccountsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 2 - assert data["data"][0]["amount"] == 1000.00 - assert data["data"][0]["currency"] == "EUR" - assert data["data"][0]["balance_type"] == "interimAvailable" + assert len(data) == 2 + assert data[0]["amount"] == 1000.00 + assert data[0]["currency"] == "EUR" + assert data[0]["balance_type"] == "interimAvailable" def test_get_account_transactions_success( self, @@ -212,10 +208,9 @@ class TestAccountsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 1 + assert len(data) == 1 - transaction = data["data"][0] + transaction = data[0] assert transaction["internal_transaction_id"] == "txn-123" assert transaction["amount"] == -10.50 assert transaction["currency"] == "EUR" @@ -264,10 +259,9 @@ class TestAccountsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 1 + assert len(data) == 1 - transaction = data["data"][0] + transaction = data[0] assert transaction["internal_transaction_id"] == "txn-123" assert transaction["institution_id"] == "REVOLUT_REVOLT21" assert transaction["iban"] == "LT313250081177977789" @@ -321,9 +315,8 @@ class TestAccountsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["id"] == "test-account-123" - assert data["data"]["display_name"] == "My Custom Account Name" + assert data["id"] == "test-account-123" + assert data["display_name"] == "My Custom Account Name" def test_update_account_not_found( self, api_client, mock_config, mock_auth_token, mock_db_path diff --git a/tests/unit/test_api_backup.py b/tests/unit/test_api_backup.py index 52f077d..8f9dfac 100644 --- a/tests/unit/test_api_backup.py +++ b/tests/unit/test_api_backup.py @@ -19,8 +19,7 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["s3"] is None + assert data["s3"] is None def test_get_backup_settings_with_s3_config(self, api_client, mock_config): """Test getting backup settings with S3 configuration.""" @@ -42,10 +41,9 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["s3"] is not None + assert data["s3"] is not None - s3_config = data["data"]["s3"] + s3_config = data["s3"] assert s3_config["access_key_id"] == "***" # Masked assert s3_config["secret_access_key"] == "***" # Masked assert s3_config["bucket_name"] == "test-bucket" @@ -77,8 +75,7 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["updated"] is True + assert data["updated"] is True # Verify connection test was called mock_test_connection.assert_called_once() @@ -132,8 +129,7 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["connected"] is True + assert data["connected"] is True # Verify connection test was called mock_test_connection.assert_called_once() @@ -158,9 +154,9 @@ class TestBackupAPI: response = api_client.post("/api/v1/backup/test", json=request_data) - assert response.status_code == 200 + assert response.status_code == 400 data = response.json() - assert data["success"] is False + assert "S3 connection test failed" in data["detail"] def test_test_backup_connection_invalid_service(self, api_client): """Test backup connection test with invalid service.""" @@ -214,10 +210,9 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 2 + assert len(data) == 2 assert ( - data["data"][0]["key"] + data[0]["key"] == "leggen_backups/database_backup_20250101_120000.db" ) @@ -230,8 +225,7 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"] == [] + assert data == [] @patch("leggen.services.backup_service.BackupService.backup_database") @patch("leggen.utils.paths.path_manager.get_database_path") @@ -261,9 +255,8 @@ class TestBackupAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["operation"] == "backup" - assert data["data"]["completed"] is True + assert data["operation"] == "backup" + assert data["completed"] is True # Verify backup was called mock_backup_db.assert_called_once() diff --git a/tests/unit/test_api_banks.py b/tests/unit/test_api_banks.py index 1e08456..03eae74 100644 --- a/tests/unit/test_api_banks.py +++ b/tests/unit/test_api_banks.py @@ -33,10 +33,9 @@ class TestBanksAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 2 - assert data["data"][0]["id"] == "REVOLUT_REVOLT21" - assert data["data"][1]["id"] == "BANCOBPI_BBPIPTPL" + assert len(data) == 2 + assert data[0]["id"] == "REVOLUT_REVOLT21" + assert data[1]["id"] == "BANCOBPI_BBPIPTPL" @respx.mock def test_get_institutions_invalid_country(self, api_client, mock_config): @@ -92,9 +91,8 @@ class TestBanksAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert data["data"]["id"] == "req-123" - assert data["data"]["institution_id"] == "REVOLUT_REVOLT21" + assert data["id"] == "req-123" + assert data["institution_id"] == "REVOLUT_REVOLT21" @respx.mock def test_get_bank_status_success(self, api_client, mock_config, mock_auth_token): @@ -128,10 +126,9 @@ class TestBanksAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) == 1 - assert data["data"][0]["bank_id"] == "REVOLUT_REVOLT21" - assert data["data"][0]["status_display"] == "LINKED" + assert len(data) == 1 + assert data[0]["bank_id"] == "REVOLUT_REVOLT21" + assert data[0]["status_display"] == "LINKED" def test_get_supported_countries(self, api_client): """Test supported countries endpoint.""" @@ -139,11 +136,10 @@ class TestBanksAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - assert len(data["data"]) > 0 + assert len(data) > 0 # Check some expected countries - country_codes = [country["code"] for country in data["data"]] + country_codes = [country["code"] for country in data] assert "PT" in country_codes assert "GB" in country_codes assert "DE" in country_codes diff --git a/tests/unit/test_api_transactions.py b/tests/unit/test_api_transactions.py index 7e637d1..14840f1 100644 --- a/tests/unit/test_api_transactions.py +++ b/tests/unit/test_api_transactions.py @@ -58,7 +58,6 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True assert len(data["data"]) == 2 # Check first transaction summary @@ -105,7 +104,6 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True assert len(data["data"]) == 1 transaction = data["data"][0] @@ -160,7 +158,6 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True # Verify the database service was called with correct filters mock_get_transactions.assert_called_once_with( @@ -193,11 +190,10 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True assert len(data["data"]) == 0 - assert data["pagination"]["total"] == 0 - assert data["pagination"]["page"] == 1 - assert data["pagination"]["total_pages"] == 0 + assert data["total"] == 0 + assert data["page"] == 1 + assert data["total_pages"] == 0 def test_get_transactions_database_error( self, api_client, mock_config, mock_auth_token @@ -254,21 +250,19 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - stats = data["data"] - assert stats["period_days"] == 30 - assert stats["total_transactions"] == 3 - assert stats["booked_transactions"] == 2 - assert stats["pending_transactions"] == 1 - assert stats["total_income"] == 100.00 - assert stats["total_expenses"] == 35.80 # abs(-10.50) + abs(-25.30) - assert stats["net_change"] == 64.20 # 100.00 - 35.80 - assert stats["accounts_included"] == 2 # Two unique account IDs + assert data["period_days"] == 30 + assert data["total_transactions"] == 3 + assert data["booked_transactions"] == 2 + assert data["pending_transactions"] == 1 + assert data["total_income"] == 100.00 + assert data["total_expenses"] == 35.80 # abs(-10.50) + abs(-25.30) + assert data["net_change"] == 64.20 # 100.00 - 35.80 + assert data["accounts_included"] == 2 # Two unique account IDs # Average transaction: ((-10.50) + 100.00 + (-25.30)) / 3 = 64.20 / 3 = 21.4 expected_avg = round(64.20 / 3, 2) - assert stats["average_transaction"] == expected_avg + assert data["average_transaction"] == expected_avg def test_get_transaction_stats_with_account_filter( self, api_client, mock_config, mock_auth_token @@ -317,15 +311,13 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["success"] is True - stats = data["data"] - assert stats["total_transactions"] == 0 - assert stats["total_income"] == 0.0 - assert stats["total_expenses"] == 0.0 - assert stats["net_change"] == 0.0 - assert stats["average_transaction"] == 0 # Division by zero handled - assert stats["accounts_included"] == 0 + assert data["total_transactions"] == 0 + assert data["total_income"] == 0.0 + assert data["total_expenses"] == 0.0 + assert data["net_change"] == 0.0 + assert data["average_transaction"] == 0 # Division by zero handled + assert data["accounts_included"] == 0 def test_get_transaction_stats_database_error( self, api_client, mock_config, mock_auth_token @@ -368,7 +360,7 @@ class TestTransactionsAPI: assert response.status_code == 200 data = response.json() - assert data["data"]["period_days"] == 7 + assert data["period_days"] == 7 # Verify the date range was calculated correctly for 7 days mock_get_transactions.assert_called_once()