From aaf4069cbf74ab476fae77d5caaba4cf44e11c3c Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Wed, 6 Aug 2025 06:44:14 +0800 Subject: [PATCH 1/4] BLOG-90 feat: integrate Sentry for error tracking and performance monitoring - Added Sentry initialization in both client and server hooks. - Configured Sentry with environment variables for DSN and sampling rates. - Implemented error handling with Sentry in server hooks. - Updated environment configuration to include SENTRY_DSN. - Configured Vite to upload source maps to Sentry. --- frontend/.gitignore | 3 + frontend/Dockerfile | 2 + frontend/package.json | 5 +- frontend/pnpm-lock.yaml | 1550 ++++++++++++++++++++++++++++++- frontend/src/hooks.client.ts | 26 + frontend/src/hooks.server.ts | 15 +- frontend/src/lib/environment.ts | 1 + frontend/vite.config.ts | 12 +- 8 files changed, 1585 insertions(+), 29 deletions(-) create mode 100644 frontend/src/hooks.client.ts diff --git a/frontend/.gitignore b/frontend/.gitignore index 3b462cb..61e28fe 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -21,3 +21,6 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Sentry Config File +.env.sentry-build-plugin diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 69e2299..92742a1 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -23,6 +23,8 @@ EXPOSE 3000 ENV NODE_ENV=production ENV HOSTNAME=0.0.0.0 ENV PORT=3000 +ENV SENTRY_AUTH_TOKEN= +ENV PUBLIC_SENTRY_DSN= ENV PUBLIC_API_BASE_URL=http://127.0.0.1:8080/ ENV PUBLIC_GA_MEASUREMENT_ID= CMD ["node", "build"] diff --git a/frontend/package.json b/frontend/package.json index 17a15b7..f8e895b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,5 +47,8 @@ "esbuild" ] }, - "packageManager": "pnpm@10.12.4" + "packageManager": "pnpm@10.12.4", + "dependencies": { + "@sentry/sveltekit": "^10.1.0" + } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 0d4e6d0..65c56df 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + '@sentry/sveltekit': + specifier: ^10.1.0 + version: 10.1.0(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) devDependencies: '@eslint/compat': specifier: ^1.2.5 @@ -19,22 +23,22 @@ importers: version: 7.0.0 '@sveltejs/adapter-auto': specifier: ^6.0.0 - version: 6.0.1(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))) + version: 6.0.1(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))) '@sveltejs/adapter-node': specifier: ^5.2.13 - version: 5.2.13(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))) + version: 5.2.13(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))) '@sveltejs/kit': specifier: ^2.22.0 - version: 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + version: 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) '@sveltejs/vite-plugin-svelte': specifier: ^6.0.0 - version: 6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + version: 6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.16(tailwindcss@4.1.11) '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.1.11(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + version: 4.1.11(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) '@types/markdown-it': specifier: ^14.1.2 version: 14.1.2 @@ -85,7 +89,7 @@ importers: version: 8.38.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) vite: specifier: ^7.0.4 - version: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1) + version: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) zod: specifier: ^4.0.5 version: 4.0.5 @@ -96,6 +100,78 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.9': + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + '@esbuild/aix-ppc64@0.25.8': resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} engines: {node: '>=18'} @@ -352,9 +428,208 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@opentelemetry/api-logs@0.203.0': + resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.57.2': + resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.0.1': + resolution: {integrity: sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.0.1': + resolution: {integrity: sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/instrumentation-amqplib@0.50.0': + resolution: {integrity: sha512-kwNs/itehHG/qaQBcVrLNcvXVPW0I4FCOVtw3LHMLdYIqD7GJ6Yv2nX+a4YHjzbzIeRYj8iyMp0Bl7tlkidq5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.47.0': + resolution: {integrity: sha512-pjenvjR6+PMRb6/4X85L4OtkQCootgb/Jzh/l/Utu3SJHBid1F+gk9sTGU2FWuhhEfV6P7MZ7BmCdHXQjgJ42g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dataloader@0.21.0': + resolution: {integrity: sha512-Xu4CZ1bfhdkV3G6iVHFgKTgHx8GbKSqrTU01kcIJRGHpowVnyOPEv1CW5ow+9GU2X4Eki8zoNuVUenFc3RluxQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.52.0': + resolution: {integrity: sha512-W7pizN0Wh1/cbNhhTf7C62NpyYw7VfCFTYg0DYieSTrtPBT1vmoSZei19wfKLnrMsz3sHayCg0HxCVL2c+cz5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.23.0': + resolution: {integrity: sha512-Puan+QopWHA/KNYvDfOZN6M/JtF6buXEyD934vrb8WhsX1/FuM7OtoMlQyIqAadnE8FqqDL4KDPiEfCQH6pQcQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.47.0': + resolution: {integrity: sha512-UfHqf3zYK+CwDwEtTjaD12uUqGGTswZ7ofLBEdQ4sEJp9GHSSJMQ2hT3pgBxyKADzUdoxQAv/7NqvL42ZI+Qbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.51.0': + resolution: {integrity: sha512-LchkOu9X5DrXAnPI1+Z06h/EH/zC7D6sA86hhPrk3evLlsJTz0grPrkL/yUJM9Ty0CL/y2HSvmWQCjbJEz/ADg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.50.0': + resolution: {integrity: sha512-5xGusXOFQXKacrZmDbpHQzqYD1gIkrMWuwvlrEPkYOsjUqGUjl1HbxCsn5Y9bUXOCgP1Lj6A4PcKt1UiJ2MujA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.203.0': + resolution: {integrity: sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.51.0': + resolution: {integrity: sha512-9IUws0XWCb80NovS+17eONXsw1ZJbHwYYMXiwsfR9TSurkLV5UNbRSKb9URHO+K+pIJILy9wCxvyiOneMr91Ig==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.12.0': + resolution: {integrity: sha512-bIe4aSAAxytp88nzBstgr6M7ZiEpW6/D1/SuKXdxxuprf18taVvFL2H5BDNGZ7A14K27haHqzYqtCTqFXHZOYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.48.0': + resolution: {integrity: sha512-V5wuaBPv/lwGxuHjC6Na2JFRjtPgstw19jTFl1B1b6zvaX8zVDYUDaR5hL7glnQtUSCMktPttQsgK4dhXpddcA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.51.0': + resolution: {integrity: sha512-XNLWeMTMG1/EkQBbgPYzCeBD0cwOrfnn8ao4hWgLv0fNCFQu1kCsJYygz2cvKuCs340RlnG4i321hX7R8gj3Rg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.48.0': + resolution: {integrity: sha512-KUW29wfMlTPX1wFz+NNrmE7IzN7NWZDrmFWHM/VJcmFEuQGnnBuTIdsP55CnBDxKgQ/qqYFp4udQFNtjeFosPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.56.0': + resolution: {integrity: sha512-YG5IXUUmxX3Md2buVMvxm9NWlKADrnavI36hbJsihqqvBGsWnIfguf0rUP5Srr0pfPqhQjUP+agLMsvu0GmUpA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.50.0': + resolution: {integrity: sha512-Am8pk1Ct951r4qCiqkBcGmPIgGhoDiFcRtqPSLbJrUZqEPUsigjtMjoWDRLG1Ki1NHgOF7D0H7d+suWz1AAizw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.49.0': + resolution: {integrity: sha512-dCub9wc02mkJWNyHdVEZ7dvRzy295SmNJa+LrAJY2a/+tIiVBQqEAajFzKwp9zegVVnel9L+WORu34rGLQDzxA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.49.0': + resolution: {integrity: sha512-QU9IUNqNsrlfE3dJkZnFHqLjlndiU39ll/YAAEvWE40sGOCi9AtOF6rmEGzJ1IswoZ3oyePV7q2MP8SrhJfVAA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.55.0': + resolution: {integrity: sha512-yfJ5bYE7CnkW/uNsnrwouG/FR7nmg09zdk2MSs7k0ZOMkDDAE3WBGpVFFApGgNu2U+gtzLgEzOQG4I/X+60hXw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis@0.51.0': + resolution: {integrity: sha512-uL/GtBA0u72YPPehwOvthAe+Wf8k3T+XQPBssJmTYl6fzuZjNq8zTfxVFhl9nRFjFVEe+CtiYNT0Q3AyqW1Z0A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.22.0': + resolution: {integrity: sha512-XrrNSUCyEjH1ax9t+Uo6lv0S2FCCykcF7hSxBMxKf7Xn0bPRxD3KyFUZy25aQXzbbbUHhtdxj3r2h88SfEM3aA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.14.0': + resolution: {integrity: sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation@0.203.0': + resolution: {integrity: sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.57.2': + resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/redis-common@0.38.0': + resolution: {integrity: sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@opentelemetry/resources@2.0.1': + resolution: {integrity: sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.0.1': + resolution: {integrity: sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.36.0': + resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==} + engines: {node: '>=14'} + + '@opentelemetry/sql-common@0.41.0': + resolution: {integrity: sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@prisma/instrumentation@6.12.0': + resolution: {integrity: sha512-UfwLME9uRDKGOu06Yrj5ERT5XVx4xvdyPsjRtQl2gY2ZgSK6c2ZNsKfEPVQHwrNl4hu2m9Rw1KCcy0sdEnefKw==} + peerDependencies: + '@opentelemetry/api': ^1.8 + '@rollup/plugin-commonjs@28.0.6': resolution: {integrity: sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==} engines: {node: '>=16.0.0 || 14 >= 14.17'} @@ -491,6 +766,145 @@ packages: cpu: [x64] os: [win32] + '@sentry-internal/browser-utils@10.1.0': + resolution: {integrity: sha512-9g9EOlCcBtGipCQKF3Y+8Z+GQFLrc2NxOr7sYnys0uELtcYE4AxZsyHKERz5VNb/sjpNuAscL1i2YBykKp2QeA==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.1.0': + resolution: {integrity: sha512-u4Y3ZBAWbx3UZXOcfMCFR3Qzqozxuj1eQLLURlUTj+jKtCuWrqSYvb5CynuRgo+EDwPFdc5DA8PmNXjGJgvkkA==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.1.0': + resolution: {integrity: sha512-mfdG6bHqVkvQlcYSlHh72UScJjZdLUqkf0fW4T5qyrK8b4omWX/ieFmr8DwtUdOu0iArr1kN4E/83WFVpRhJfg==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.1.0': + resolution: {integrity: sha512-gfuWCCslNg80XMcKFCV8RU14MR06XrUbJ3CsxrOD4cx9biA5EfBdy06AeA5Q3l3KCYzVzLAZH2rS/Pdvr2hrPA==} + engines: {node: '>=18'} + + '@sentry/babel-plugin-component-annotate@4.0.2': + resolution: {integrity: sha512-Nr/VamvpQs6w642EI5t+qaCUGnVEro0qqk+S8XO1gc8qSdpc8kkZJFnUk7ozAr+ljYWGfVgWXrxI9lLiriLsRA==} + engines: {node: '>= 14'} + + '@sentry/browser@10.1.0': + resolution: {integrity: sha512-KevWtdmcxjREOVkfGUlXhB7Yj2K5AZwIDvdyQg9044MM5M2hxMWWIpsJewF+f2V0rZN4QYgxscpe4NKXg5GBYQ==} + engines: {node: '>=18'} + + '@sentry/bundler-plugin-core@4.0.2': + resolution: {integrity: sha512-LeARs8qHhEw19tk+KZd9DDV+Rh/UeapIH0+C09fTmff9p8Y82Cj89pEQ2a1rdUiF/oYIjQX45vnZscB7ra42yw==} + engines: {node: '>= 14'} + + '@sentry/cli-darwin@2.50.2': + resolution: {integrity: sha512-0Pjpl0vQqKhwuZm19z6AlEF+ds3fJg1KWabv8WzGaSc/fwxMEwjFwOZj+IxWBJPV578cXXNvB39vYjjpCH8j7A==} + engines: {node: '>=10'} + os: [darwin] + + '@sentry/cli-linux-arm64@2.50.2': + resolution: {integrity: sha512-03Cj215M3IdoHAwevCxm5oOm9WICFpuLR05DQnODFCeIUsGvE1pZsc+Gm0Ky/ZArq2PlShBJTpbHvXbCUka+0w==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux, freebsd, android] + + '@sentry/cli-linux-arm@2.50.2': + resolution: {integrity: sha512-jzFwg9AeeuFAFtoCcyaDEPG05TU02uOy1nAX09c1g7FtsyQlPcbhI94JQGmnPzdRjjDmORtwIUiVZQrVTkDM7w==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux, freebsd, android] + + '@sentry/cli-linux-i686@2.50.2': + resolution: {integrity: sha512-J+POvB34uVyHbIYF++Bc/OCLw+gqKW0H/y/mY7rRZCiocgpk266M4NtsOBl6bEaurMx1D+BCIEjr4nc01I/rqA==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [linux, freebsd, android] + + '@sentry/cli-linux-x64@2.50.2': + resolution: {integrity: sha512-81yQVRLj8rnuHoYcrM7QbOw8ubA3weiMdPtTxTim1s6WExmPgnPTKxLCr9xzxGJxFdYo3xIOhtf5JFpUX/3j4A==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux, freebsd, android] + + '@sentry/cli-win32-arm64@2.50.2': + resolution: {integrity: sha512-QjentLGvpibgiZlmlV9ifZyxV73lnGH6pFZWU5wLeRiaYKxWtNrrHpVs+HiWlRhkwQ0mG1/S40PGNgJ20DJ3gA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@sentry/cli-win32-i686@2.50.2': + resolution: {integrity: sha512-UkBIIzkQkQ1UkjQX8kHm/+e7IxnEhK6CdgSjFyNlxkwALjDWHJjMztevqAPz3kv4LdM6q1MxpQ/mOqXICNhEGg==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [win32] + + '@sentry/cli-win32-x64@2.50.2': + resolution: {integrity: sha512-tE27pu1sRRub1Jpmemykv3QHddBcyUk39Fsvv+n4NDpQyMgsyVPcboxBZyby44F0jkpI/q3bUH2tfCB1TYDNLg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@sentry/cli@2.50.2': + resolution: {integrity: sha512-m1L9shxutF3WHSyNld6Y1vMPoXfEyQhoRh1V3SYSdl+4AB40U+zr2sRzFa2OPm7XP4zYNaWuuuHLkY/iHITs8Q==} + engines: {node: '>= 10'} + hasBin: true + + '@sentry/cloudflare@10.1.0': + resolution: {integrity: sha512-ZMa5eFvh1zIGcxUq/JK/EXx18Ce5lvt8ImNJOjWa+T9GUmmhBtBjtOgzevmx7UAciMd8OVjujH6s4vMEqQPjCQ==} + engines: {node: '>=18'} + peerDependencies: + '@cloudflare/workers-types': ^4.x + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + '@sentry/core@10.1.0': + resolution: {integrity: sha512-sda76pKjgEgh6VNRNJa9P0WG2egA7N85dTQ8wS/QZ6rD/dNXQZ+ITfL99Jb0b6WhTgvzspJq8bNSl/nRUdbwbQ==} + engines: {node: '>=18'} + + '@sentry/node-core@10.1.0': + resolution: {integrity: sha512-v9riYu689y/jJ3aPYjpLguwBAIE7B0wGHbJhU0OPxAHuieqIzaLlPvmlMO3hLQRxVBI/Ht2aA5Fqnx24x1hQZQ==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.0.0 + '@opentelemetry/core': ^1.30.1 || ^2.0.0 + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/resources': ^1.30.1 || ^2.0.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 + '@opentelemetry/semantic-conventions': ^1.34.0 + + '@sentry/node@10.1.0': + resolution: {integrity: sha512-A+mM6Mz5aVGQkV+HWD+1xDAr+ineJ3BT3y3nXWB1ev9EMO3W+2ZeQ6BZMJjy2Yrak8FjZpeHnsvBLHppxZmEow==} + engines: {node: '>=18'} + + '@sentry/opentelemetry@10.1.0': + resolution: {integrity: sha512-REwbS5UgNI4bQ4zovUzE2C0ppv4Fvxq48o8r/0ldvR1pmFi6P7kAmz5ixUonrXCy2XtX4Zs5wZmbZ7Am5hzmRQ==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.0.0 + '@opentelemetry/core': ^1.30.1 || ^2.0.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 + '@opentelemetry/semantic-conventions': ^1.34.0 + + '@sentry/svelte@10.1.0': + resolution: {integrity: sha512-d5NNXzaw5Svxkf8hhZx8gZ2VgMMNhCj22T/Cr5UHYU/QBLH3TEo9NYhNZjPBSslLu88T4aCqiFivTrGASXl0qg==} + engines: {node: '>=18'} + peerDependencies: + svelte: 3.x || 4.x || 5.x + + '@sentry/sveltekit@10.1.0': + resolution: {integrity: sha512-aGhP4mHltNF97sQ7qXo1Z8alFEcLcXAG98rgyzOBmpSqIC0TW6TZjjYMZiQjRI/kcEH9dJTq0h4ad1Yfykh/NQ==} + engines: {node: '>=18'} + peerDependencies: + '@sveltejs/kit': 2.x + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + '@sentry/vite-plugin@4.0.2': + resolution: {integrity: sha512-rStdvDGj0VcJBsl2NANE3OQF5m13VCJ+2Ovs5pb2LAqOW72/1GLmef9Mo3b9BwzCjWOQIcgtllwOfSV5Pse03w==} + engines: {node: '>= 14'} + '@sveltejs/acorn-typescript@1.0.5': resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} peerDependencies: @@ -625,6 +1039,9 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -643,12 +1060,30 @@ packages: '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + + '@types/node@24.2.0': + resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} + + '@types/pg-pool@2.0.6': + resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} + + '@types/pg@8.15.4': + resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} '@types/sanitize-html@2.16.0': resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@typescript-eslint/eslint-plugin@8.38.0': resolution: {integrity: sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -708,6 +1143,11 @@ packages: resolution: {integrity: sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -718,6 +1158,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -725,6 +1169,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -732,6 +1180,10 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -739,6 +1191,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -749,14 +1205,26 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001731: + resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -765,6 +1233,9 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -782,6 +1253,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -831,6 +1305,13 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + electron-to-chromium@1.5.195: + resolution: {integrity: sha512-URclP0iIaDUzqcAyV1v2PgduJ9N0IdXmWsnPzPfelvBmjmZzEy6xJcjb1cXj+TbYqXgtLrjHEoaSIdTYhw4ezg==} + enhanced-resolve@5.18.2: resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} engines: {node: '>=10.13.0'} @@ -844,6 +1325,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -893,6 +1378,11 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -958,6 +1448,12 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -966,6 +1462,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -974,6 +1474,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -982,6 +1486,12 @@ packages: resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} engines: {node: '>=18'} + globalyzer@0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -999,6 +1509,10 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1011,10 +1525,17 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@1.14.2: + resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -1051,10 +1572,18 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1064,6 +1593,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1165,9 +1699,23 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} + engines: {node: '>=12'} + + magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true @@ -1186,10 +1734,21 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -1203,6 +1762,9 @@ packages: engines: {node: '>=10'} hasBin: true + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1222,6 +1784,22 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1252,6 +1830,21 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1299,6 +1892,22 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1375,6 +1984,13 @@ packages: engines: {node: '>=14'} hasBin: true + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1386,10 +2002,22 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1418,6 +2046,10 @@ packages: sanitize-html@2.17.0: resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -1434,14 +2066,25 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + sirv@3.0.1: resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} + sorcery@1.0.0: + resolution: {integrity: sha512-5ay9oJE+7sNmhzl3YNG18jEEEf4AOQCM/FAqR5wMmzqd1FtRorFbJXn3w3SKOhbiQaVgHM+Q1lszZspjri7bpA==} + hasBin: true + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1486,6 +2129,12 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + tiny-glob@0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -1498,12 +2147,18 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1523,6 +2178,18 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + unplugin@1.0.1: + resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -1577,6 +2244,19 @@ packages: vite: optional: true + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-sources@3.3.3: + resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + engines: {node: '>=10.13.0'} + + webpack-virtual-modules@0.5.0: + resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1586,6 +2266,13 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -1611,6 +2298,110 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.28.2 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.2': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.26.9': + dependencies: + '@babel/types': 7.28.2 + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.2 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@esbuild/aix-ppc64@0.25.8': optional: true @@ -1782,8 +2573,267 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@opentelemetry/api-logs@0.203.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.57.2': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.36.0 + + '@opentelemetry/instrumentation-amqplib@0.50.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.47.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.21.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.52.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.23.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.47.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.50.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.0 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.48.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.48.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.50.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.49.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/sql-common': 0.41.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.49.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/sql-common': 0.41.0(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.4 + '@types/pg-pool': 2.0.6 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.0 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.22.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.14.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.2 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + semver: 7.7.2 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/redis-common@0.38.0': {} + + '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + + '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + + '@opentelemetry/semantic-conventions@1.36.0': {} + + '@opentelemetry/sql-common@0.41.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@polka/url@1.0.0-next.29': {} + '@prisma/instrumentation@6.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@rollup/plugin-commonjs@28.0.6(rollup@4.45.1)': dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.45.1) @@ -1880,26 +2930,216 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.45.1': optional: true + '@sentry-internal/browser-utils@10.1.0': + dependencies: + '@sentry/core': 10.1.0 + + '@sentry-internal/feedback@10.1.0': + dependencies: + '@sentry/core': 10.1.0 + + '@sentry-internal/replay-canvas@10.1.0': + dependencies: + '@sentry-internal/replay': 10.1.0 + '@sentry/core': 10.1.0 + + '@sentry-internal/replay@10.1.0': + dependencies: + '@sentry-internal/browser-utils': 10.1.0 + '@sentry/core': 10.1.0 + + '@sentry/babel-plugin-component-annotate@4.0.2': {} + + '@sentry/browser@10.1.0': + dependencies: + '@sentry-internal/browser-utils': 10.1.0 + '@sentry-internal/feedback': 10.1.0 + '@sentry-internal/replay': 10.1.0 + '@sentry-internal/replay-canvas': 10.1.0 + '@sentry/core': 10.1.0 + + '@sentry/bundler-plugin-core@4.0.2': + dependencies: + '@babel/core': 7.28.0 + '@sentry/babel-plugin-component-annotate': 4.0.2 + '@sentry/cli': 2.50.2 + dotenv: 16.6.1 + find-up: 5.0.0 + glob: 9.3.5 + magic-string: 0.30.8 + unplugin: 1.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/cli-darwin@2.50.2': + optional: true + + '@sentry/cli-linux-arm64@2.50.2': + optional: true + + '@sentry/cli-linux-arm@2.50.2': + optional: true + + '@sentry/cli-linux-i686@2.50.2': + optional: true + + '@sentry/cli-linux-x64@2.50.2': + optional: true + + '@sentry/cli-win32-arm64@2.50.2': + optional: true + + '@sentry/cli-win32-i686@2.50.2': + optional: true + + '@sentry/cli-win32-x64@2.50.2': + optional: true + + '@sentry/cli@2.50.2': + dependencies: + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + optionalDependencies: + '@sentry/cli-darwin': 2.50.2 + '@sentry/cli-linux-arm': 2.50.2 + '@sentry/cli-linux-arm64': 2.50.2 + '@sentry/cli-linux-i686': 2.50.2 + '@sentry/cli-linux-x64': 2.50.2 + '@sentry/cli-win32-arm64': 2.50.2 + '@sentry/cli-win32-i686': 2.50.2 + '@sentry/cli-win32-x64': 2.50.2 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/cloudflare@10.1.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@sentry/core': 10.1.0 + + '@sentry/core@10.1.0': {} + + '@sentry/node-core@10.1.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@sentry/core': 10.1.0 + '@sentry/opentelemetry': 10.1.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0) + import-in-the-middle: 1.14.2 + + '@sentry/node@10.1.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.50.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.47.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.21.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.23.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.47.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.50.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.48.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.48.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.50.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.49.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.49.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.51.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.22.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.14.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@prisma/instrumentation': 6.12.0(@opentelemetry/api@1.9.0) + '@sentry/core': 10.1.0 + '@sentry/node-core': 10.1.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0) + '@sentry/opentelemetry': 10.1.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0) + import-in-the-middle: 1.14.2 + minimatch: 9.0.5 + transitivePeerDependencies: + - supports-color + + '@sentry/opentelemetry@10.1.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + '@sentry/core': 10.1.0 + + '@sentry/svelte@10.1.0(svelte@5.36.13)': + dependencies: + '@sentry/browser': 10.1.0 + '@sentry/core': 10.1.0 + magic-string: 0.30.17 + svelte: 5.36.13 + + '@sentry/sveltekit@10.1.0(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@babel/parser': 7.26.9 + '@sentry/cloudflare': 10.1.0 + '@sentry/core': 10.1.0 + '@sentry/node': 10.1.0 + '@sentry/svelte': 10.1.0(svelte@5.36.13) + '@sentry/vite-plugin': 4.0.2 + '@sveltejs/kit': 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) + magic-string: 0.30.7 + recast: 0.23.11 + sorcery: 1.0.0 + optionalDependencies: + vite: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - encoding + - supports-color + - svelte + + '@sentry/vite-plugin@4.0.2': + dependencies: + '@sentry/bundler-plugin-core': 4.0.2 + unplugin: 1.0.1 + transitivePeerDependencies: + - encoding + - supports-color + '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@6.0.1(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))': + '@sveltejs/adapter-auto@6.0.1(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))': dependencies: - '@sveltejs/kit': 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/kit': 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) - '@sveltejs/adapter-node@5.2.13(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))': + '@sveltejs/adapter-node@5.2.13(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))': dependencies: '@rollup/plugin-commonjs': 28.0.6(rollup@4.45.1) '@rollup/plugin-json': 6.1.0(rollup@4.45.1) '@rollup/plugin-node-resolve': 16.0.1(rollup@4.45.1) - '@sveltejs/kit': 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/kit': 2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) rollup: 4.45.1 - '@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 @@ -1912,27 +3152,27 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.1 svelte: 5.36.13 - vite: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) - '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) debug: 4.4.1 svelte: 5.36.13 - vite: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) debug: 4.4.1 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.36.13 - vite: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1) - vitefu: 1.1.1(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) + vite: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + vitefu: 1.1.1(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)) transitivePeerDependencies: - supports-color @@ -2008,12 +3248,16 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.1.11 - '@tailwindcss/vite@4.1.11(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.11(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@tailwindcss/node': 4.1.11 '@tailwindcss/oxide': 4.1.11 tailwindcss: 4.1.11 - vite: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.2.0 '@types/cookie@0.6.0': {} @@ -2030,12 +3274,36 @@ snapshots: '@types/mdurl@2.0.0': {} + '@types/mysql@2.15.27': + dependencies: + '@types/node': 24.2.0 + + '@types/node@24.2.0': + dependencies: + undici-types: 7.10.0 + + '@types/pg-pool@2.0.6': + dependencies: + '@types/pg': 8.15.4 + + '@types/pg@8.15.4': + dependencies: + '@types/node': 24.2.0 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + '@types/resolve@1.20.2': {} '@types/sanitize-html@2.16.0': dependencies: htmlparser2: 8.0.2 + '@types/shimmer@1.2.0': {} + + '@types/tedious@4.0.14': + dependencies: + '@types/node': 24.2.0 + '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -2129,12 +3397,22 @@ snapshots: '@typescript-eslint/types': 8.38.0 eslint-visitor-keys: 4.2.1 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2146,14 +3424,25 @@ snapshots: dependencies: color-convert: 2.0.1 + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + argparse@2.0.1: {} aria-query@5.3.2: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + axobject-query@4.1.0: {} balanced-match@1.0.2: {} + binary-extensions@2.3.0: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2167,19 +3456,42 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001731 + electron-to-chromium: 1.5.195 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + callsites@3.1.0: {} + caniuse-lite@1.0.30001731: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 chownr@3.0.0: {} + cjs-module-lexer@1.4.3: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -2192,6 +3504,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cookie@0.6.0: {} cross-spawn@7.0.6: @@ -2232,6 +3546,10 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dotenv@16.6.1: {} + + electron-to-chromium@1.5.195: {} + enhanced-resolve@5.18.2: dependencies: graceful-fs: 4.2.11 @@ -2268,6 +3586,8 @@ snapshots: '@esbuild/win32-ia32': 0.25.8 '@esbuild/win32-x64': 0.25.8 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@9.31.0(jiti@2.4.2)): @@ -2351,6 +3671,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -2411,11 +3733,17 @@ snapshots: flatted@3.3.3: {} + forwarded-parse@2.1.2: {} + + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2424,10 +3752,21 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@9.3.5: + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.11.1 + globals@14.0.0: {} globals@16.3.0: {} + globalyzer@0.1.0: {} + + globrex@0.1.2: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -2445,6 +3784,13 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2454,8 +3800,19 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@1.14.2: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + imurmurhash@0.1.4: {} + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -2484,16 +3841,22 @@ snapshots: jiti@2.4.2: {} + js-tokens@4.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2570,10 +3933,24 @@ snapshots: lodash.merge@4.6.2: {} + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 + magic-string@0.30.7: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + + magic-string@0.30.8: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + markdown-it@14.1.0: dependencies: argparse: 2.0.1 @@ -2596,10 +3973,18 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@8.0.4: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + + minipass@4.2.8: {} + minipass@7.1.2: {} minizlib@3.0.2: @@ -2608,6 +3993,8 @@ snapshots: mkdirp@3.0.1: {} + module-details-from-path@1.0.4: {} + mri@1.2.0: {} mrmime@2.0.1: {} @@ -2618,6 +4005,14 @@ snapshots: natural-compare@1.4.0: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2647,6 +4042,23 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pg-int8@1.0.1: {} + + pg-protocol@1.10.3: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -2684,6 +4096,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prelude-ls@1.2.1: {} prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.36.13): @@ -2699,14 +4121,38 @@ snapshots: prettier@3.6.2: {} + progress@2.0.3: {} + + proxy-from-env@1.1.0: {} + punycode.js@2.3.1: {} punycode@2.3.1: {} queue-microtask@1.2.3: {} + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + readdirp@4.1.2: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.1 + module-details-from-path: 1.0.4 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + resolve-from@4.0.0: {} resolve@1.22.10: @@ -2760,6 +4206,8 @@ snapshots: parse-srcset: 1.0.2 postcss: 8.5.6 + semver@6.3.1: {} + semver@7.7.2: {} set-cookie-parser@2.7.1: {} @@ -2770,14 +4218,24 @@ snapshots: shebang-regex@3.0.0: {} + shimmer@1.2.1: {} + sirv@3.0.1: dependencies: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 totalist: 3.0.1 + sorcery@1.0.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + minimist: 1.2.8 + tiny-glob: 0.2.9 + source-map-js@1.2.1: {} + source-map@0.6.1: {} + strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -2839,6 +4297,13 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 + tiny-glob@0.2.9: + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + + tiny-invariant@1.3.3: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) @@ -2850,10 +4315,14 @@ snapshots: totalist@3.0.1: {} + tr46@0.0.3: {} + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: typescript: 5.8.3 + tslib@2.8.1: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -2873,13 +4342,28 @@ snapshots: uc.micro@2.1.0: {} + undici-types@7.10.0: {} + + unplugin@1.0.1: + dependencies: + acorn: 8.15.0 + chokidar: 3.6.0 + webpack-sources: 3.3.3 + webpack-virtual-modules: 0.5.0 + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 util-deprecate@1.0.2: {} - vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1): + vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) @@ -2888,13 +4372,25 @@ snapshots: rollup: 4.45.1 tinyglobby: 0.2.14 optionalDependencies: + '@types/node': 24.2.0 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 - vitefu@1.1.1(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)): + vitefu@1.1.1(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)): optionalDependencies: - vite: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1) + + webidl-conversions@3.0.1: {} + + webpack-sources@3.3.3: {} + + webpack-virtual-modules@0.5.0: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 which@2.0.2: dependencies: @@ -2902,6 +4398,10 @@ snapshots: word-wrap@1.2.5: {} + xtend@4.0.2: {} + + yallist@3.1.1: {} + yallist@5.0.0: {} yaml@1.10.2: {} diff --git a/frontend/src/hooks.client.ts b/frontend/src/hooks.client.ts new file mode 100644 index 0000000..5745d40 --- /dev/null +++ b/frontend/src/hooks.client.ts @@ -0,0 +1,26 @@ +import { Environment } from '$lib/environment'; +import { handleErrorWithSentry, replayIntegration } from '@sentry/sveltekit'; +import * as Sentry from '@sentry/sveltekit'; + +Sentry.init({ + dsn: Environment.SENTRY_DSN, + + tracesSampleRate: 1.0, + + // Enable logs to be sent to Sentry + enableLogs: true, + + // This sets the sample rate to be 10%. You may want this to be 100% while + // in development and sample at a lower rate in production + replaysSessionSampleRate: 0.1, + + // If the entire session is not sampled, use the below sample rate to sample + // sessions when an error occurs. + replaysOnErrorSampleRate: 1.0, + + // If you don't want to use Session Replay, just remove the line below: + integrations: [replayIntegration()] +}); + +// If you have a custom error handler, pass it to `handleErrorWithSentry` +export const handleError = handleErrorWithSentry(); diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 6c263ce..1e37de1 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -1,3 +1,5 @@ +import { sequence } from '@sveltejs/kit/hooks'; +import * as Sentry from '@sentry/sveltekit'; import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl'; import { PostBloc } from '$lib/post/adapter/presenter/postBloc'; import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc'; @@ -5,8 +7,15 @@ import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUse import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase'; import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl'; import type { Handle } from '@sveltejs/kit'; +import { Environment } from '$lib/environment'; -export const handle: Handle = ({ event, resolve }) => { +Sentry.init({ + dsn: Environment.SENTRY_DSN, + tracesSampleRate: 1, + enableLogs: true +}); + +export const handle: Handle = sequence(Sentry.sentryHandle(), ({ event, resolve }) => { const postApiService = new PostApiServiceImpl(event.fetch); const postRepository = new PostRepositoryImpl(postApiService); const getAllPostsUseCase = new GetAllPostsUseCase(postRepository); @@ -16,4 +25,6 @@ export const handle: Handle = ({ event, resolve }) => { event.locals.postBloc = new PostBloc(getPostUseCase); return resolve(event); -}; +}); + +export const handleError = Sentry.handleErrorWithSentry(); diff --git a/frontend/src/lib/environment.ts b/frontend/src/lib/environment.ts index f845aec..5c17365 100644 --- a/frontend/src/lib/environment.ts +++ b/frontend/src/lib/environment.ts @@ -3,4 +3,5 @@ import { env } from '$env/dynamic/public'; export abstract class Environment { static readonly API_BASE_URL = env.PUBLIC_API_BASE_URL ?? 'http://localhost:5173/api/'; static readonly GA_MEASUREMENT_ID = env.PUBLIC_GA_MEASUREMENT_ID ?? ''; + static readonly SENTRY_DSN = env.PUBLIC_SENTRY_DSN ?? ''; } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index dc01ee8..a3c5011 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,3 +1,4 @@ +import { sentrySvelteKit } from '@sentry/sveltekit'; import tailwindcss from '@tailwindcss/vite'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; @@ -5,7 +6,16 @@ import { defineConfig } from 'vite'; import { version } from './package.json'; export default defineConfig({ - plugins: [tailwindcss(), sveltekit()], + plugins: [ + sentrySvelteKit({ + sourceMapsUploadOptions: { + org: 'squidspirit', + project: 'blog-beta-frontend' + } + }), + tailwindcss(), + sveltekit() + ], define: { 'App.__VERSION__': JSON.stringify(version) }, -- 2.47.2 From 1900628a8f87b2c1431a59e12a8c4818452b44b0 Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Wed, 6 Aug 2025 10:36:45 +0800 Subject: [PATCH 2/4] BLOG-90 feat: integrate Sentry for error tracking and reporting - Added `anyhow` and `sentry` dependencies to the backend and feature crates. - Introduced `SentryConfiguration` to manage Sentry settings. - Updated error handling in various services to use `anyhow::Error` for unexpected errors. - Captured errors using Sentry in the web handlers for better observability. - Removed specific database error handling in favor of a more generic unexpected error handling. - Configured Sentry in the main application entry point and wrapped the Actix app with Sentry middleware. --- backend/Cargo.lock | 475 ++++++++++++++++++ backend/Cargo.toml | 2 + backend/Dockerfile | 2 + backend/feature/auth/Cargo.toml | 2 + .../auth/src/application/error/auth_error.rs | 20 +- .../src/framework/db/user_db_service_impl.rs | 6 +- .../framework/oidc/auth_oidc_service_impl.rs | 2 +- .../auth/src/framework/web/auth_middleware.rs | 34 +- .../web/get_logged_in_user_handler.rs | 8 +- .../framework/web/oidc_callback_handler.rs | 9 +- .../src/framework/web/oidc_login_handler.rs | 12 +- backend/feature/image/Cargo.toml | 2 + .../src/adapter/delivery/image_controller.rs | 2 +- .../src/application/error/image_error.rs | 19 +- .../src/framework/db/image_db_service_impl.rs | 4 +- .../framework/storage/image_storage_impl.rs | 9 +- .../framework/web/get_image_by_id_handler.rs | 8 +- .../src/framework/web/upload_image_handler.rs | 12 +- backend/feature/post/Cargo.toml | 2 + .../post/src/application/error/post_error.rs | 15 +- .../src/framework/db/label_db_service_impl.rs | 8 +- .../src/framework/db/post_db_service_impl.rs | 22 +- .../src/framework/web/create_label_handler.rs | 16 +- .../src/framework/web/create_post_handler.rs | 18 +- .../framework/web/get_all_labels_handler.rs | 12 +- .../web/get_all_post_info_handler.rs | 16 +- .../framework/web/get_post_by_id_handler.rs | 12 +- .../src/framework/web/update_label_handler.rs | 5 +- .../src/framework/web/update_post_handler.rs | 24 +- backend/server/Cargo.toml | 1 + backend/server/src/configuration.rs | 7 +- backend/server/src/configuration/sentry.rs | 22 + backend/server/src/main.rs | 57 ++- 33 files changed, 757 insertions(+), 108 deletions(-) create mode 100644 backend/server/src/configuration/sentry.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index c367e74..de4590d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -424,9 +424,11 @@ version = "0.2.0" dependencies = [ "actix-session", "actix-web", + "anyhow", "async-trait", "log", "openidconnect", + "sentry", "serde", "sqlx", "utoipa", @@ -656,6 +658,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -804,6 +816,16 @@ dependencies = [ "syn", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "der" version = "0.7.10" @@ -1081,6 +1103,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "flate2" version = "1.1.1" @@ -1114,6 +1148,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1372,6 +1421,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + [[package]] name = "http" version = "0.2.12" @@ -1465,6 +1525,22 @@ dependencies = [ "webpki-roots 1.0.2", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.16" @@ -1664,10 +1740,12 @@ version = "0.2.0" dependencies = [ "actix-multipart", "actix-web", + "anyhow", "async-trait", "auth", "futures", "log", + "sentry", "serde", "sqlx", "utoipa", @@ -1922,6 +2000,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2057,6 +2152,50 @@ dependencies = [ "url", ] +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2066,6 +2205,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_info" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +dependencies = [ + "log", + "plist", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "p256" version = "0.13.2" @@ -2199,6 +2350,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.9.0", + "quick-xml", + "serde", + "time", +] + [[package]] name = "polyval" version = "0.6.2" @@ -2231,10 +2395,12 @@ name = "post" version = "0.2.0" dependencies = [ "actix-web", + "anyhow", "async-trait", "auth", "chrono", "log", + "sentry", "serde", "sqlx", "utoipa", @@ -2273,6 +2439,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" @@ -2505,9 +2680,11 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -2518,6 +2695,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tower", "tower-http", @@ -2621,6 +2799,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.11.0" @@ -2653,6 +2840,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "schemars" version = "0.9.0" @@ -2697,12 +2893,168 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +[[package]] +name = "sentry" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989425268ab5c011e06400187eed6c298272f8ef913e49fcadc3fda788b45030" +dependencies = [ + "httpdate", + "native-tls", + "reqwest", + "sentry-actix", + "sentry-anyhow", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-actix" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c675bdf6118764a8e265c3395c311b4d905d12866c92df52870c0223d2ffc1" +dependencies = [ + "actix-http", + "actix-web", + "bytes", + "futures-util", + "sentry-core", +] + +[[package]] +name = "sentry-anyhow" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b4523c2595d6730bfbe401e95a6423fe9cb16dc3b6046f340551591cffe723" +dependencies = [ + "anyhow", + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-backtrace" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e299dd3f7bcf676875eee852c9941e1d08278a743c32ca528e2debf846a653" +dependencies = [ + "backtrace", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac0c5d6892cd4c414492fc957477b620026fb3411fca9fa12774831da561c88" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deaa38b94e70820ff3f1f9db3c8b0aef053b667be130f618e615e0ff2492cbcc" +dependencies = [ + "rand 0.9.1", + "sentry-types", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "sentry-debug-images" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00950648aa0d371c7f57057434ad5671bd4c106390df7e7284739330786a01b6" +dependencies = [ + "findshlibs", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7a23b13c004873de3ce7db86eb0f59fe4adfc655a31f7bbc17fd10bacc9bfe" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac841c7050aa73fc2bec8f7d8e9cb1159af0b3095757b99820823f3e54e5080" +dependencies = [ + "bitflags", + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e477f4d4db08ddb4ab553717a8d3a511bc9e81dde0c808c680feacbb8105c412" +dependencies = [ + "debugid", + "hex", + "rand 0.9.1", + "serde", + "serde_json", + "thiserror 2.0.12", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.219" @@ -2822,6 +3174,7 @@ dependencies = [ "openidconnect", "percent-encoding", "post", + "sentry", "sqlx", "utoipa", "utoipa-redoc", @@ -3302,6 +3655,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -3422,6 +3785,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "tracing-core", ] [[package]] @@ -3436,6 +3809,15 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -3485,6 +3867,36 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39" +dependencies = [ + "base64 0.22.1", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pemfile", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-root-certs 0.26.11", +] + +[[package]] +name = "ureq-proto" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7" +dependencies = [ + "base64 0.22.1", + "http 1.3.1", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -3497,6 +3909,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -3552,6 +3970,23 @@ dependencies = [ "utoipa", ] +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3685,6 +4120,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.2", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.10" @@ -3713,6 +4166,28 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.61.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e6cd1b2..fd697c9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -10,6 +10,7 @@ edition = "2024" actix-multipart = "0.7.2" actix-session = { version = "0.10.1", features = ["redis-session"] } actix-web = "4.10.2" +anyhow = "1.0.98" async-trait = "0.1.88" chrono = "0.4.41" dotenv = "0.15.0" @@ -22,6 +23,7 @@ openidconnect = { version = "4.0.1", features = [ "reqwest-blocking", ] } percent-encoding = "2.3.1" +sentry = { version = "0.42.0", features = ["actix", "anyhow"] } serde = { version = "1.0.219", features = ["derive"] } sqlx = { version = "0.8.5", features = [ "chrono", diff --git a/backend/Dockerfile b/backend/Dockerfile index 2b90549..1ea55b8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,6 +13,7 @@ COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/server . EXPOSE 8080 VOLUME ["/app/static"] ENV RUST_LOG=info +ENV RUST_BACKTRACE=1 ENV HOST=0.0.0.0 ENV PORT=8080 ENV STORAGE_PATH=/app/static @@ -27,5 +28,6 @@ ENV OIDC_ISSUER_URL= ENV OIDC_REDIRECT_URL= ENV OIDC_CLIENT_ID= ENV OIDC_CLIENT_SECRET= +ENV SENTRY_DSN= CMD ["./server"] diff --git a/backend/feature/auth/Cargo.toml b/backend/feature/auth/Cargo.toml index f820b30..cd238bb 100644 --- a/backend/feature/auth/Cargo.toml +++ b/backend/feature/auth/Cargo.toml @@ -6,9 +6,11 @@ edition.workspace = true [dependencies] actix-session.workspace = true actix-web.workspace = true +anyhow.workspace = true async-trait.workspace = true log.workspace = true openidconnect.workspace = true +sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true diff --git a/backend/feature/auth/src/application/error/auth_error.rs b/backend/feature/auth/src/application/error/auth_error.rs index 9fa2375..d72e6b1 100644 --- a/backend/feature/auth/src/application/error/auth_error.rs +++ b/backend/feature/auth/src/application/error/auth_error.rs @@ -1,10 +1,24 @@ -#[derive(Debug, PartialEq)] +use std::fmt::Display; + +#[derive(Debug)] pub enum AuthError { - DatabaseError(String), - OidcError(String), InvalidState, InvalidNonce, InvalidAuthCode, InvalidIdToken, UserNotFound, + Unexpected(anyhow::Error), +} + +impl Display for AuthError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthError::InvalidState => write!(f, "Invalid state"), + AuthError::InvalidNonce => write!(f, "Invalid nonce"), + AuthError::InvalidAuthCode => write!(f, "Invalid authentication code"), + AuthError::InvalidIdToken => write!(f, "Invalid ID token"), + AuthError::UserNotFound => write!(f, "User not found"), + AuthError::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } } diff --git a/backend/feature/auth/src/framework/db/user_db_service_impl.rs b/backend/feature/auth/src/framework/db/user_db_service_impl.rs index c007d3d..d4d911d 100644 --- a/backend/feature/auth/src/framework/db/user_db_service_impl.rs +++ b/backend/feature/auth/src/framework/db/user_db_service_impl.rs @@ -31,7 +31,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::DatabaseError(e.to_string()))?; + .map_err(|e| AuthError::Unexpected(e.into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -56,7 +56,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::DatabaseError(e.to_string()))?; + .map_err(|e| AuthError::Unexpected(e.into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -78,7 +78,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|e| AuthError::DatabaseError(e.to_string()))?; + .map_err(|e| AuthError::Unexpected(e.into()))?; Ok(id) } diff --git a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs index d4a85dd..037fc0c 100644 --- a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs +++ b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs @@ -80,7 +80,7 @@ impl AuthOidcService for AuthOidcServiceImpl { let token_response = self .oidc_client .exchange_code(AuthorizationCode::new(code.to_string())) - .map_err(|e| AuthError::OidcError(e.to_string()))? + .map_err(|e| AuthError::Unexpected(e.into()))? .request_async(&self.http_client) .await .map_err(|_| AuthError::InvalidAuthCode)?; diff --git a/backend/feature/auth/src/framework/web/auth_middleware.rs b/backend/feature/auth/src/framework/web/auth_middleware.rs index c1ff8d0..4abe167 100644 --- a/backend/feature/auth/src/framework/web/auth_middleware.rs +++ b/backend/feature/auth/src/framework/web/auth_middleware.rs @@ -1,7 +1,10 @@ -use std::future::{self, Ready}; +use std::{ + fmt::Display, + future::{self, Ready}, +}; use actix_session::SessionExt; -use actix_web::{Error, FromRequest, HttpRequest, dev::Payload, error::ErrorUnauthorized}; +use actix_web::{FromRequest, HttpRequest, dev::Payload}; use crate::framework::web::constants::SESSION_KEY_USER_ID; @@ -14,20 +17,39 @@ impl UserId { } impl FromRequest for UserId { - type Error = Error; - type Future = Ready>; + type Error = UnauthorizedError; + type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let user_id_result = req.get_session().get::(SESSION_KEY_USER_ID); let user_id = match user_id_result { Ok(id) => id, - _ => return future::ready(Err(ErrorUnauthorized(""))), + _ => return future::ready(Err(UnauthorizedError)), }; match user_id { Some(id) => future::ready(Ok(UserId(id))), - None => future::ready(Err(ErrorUnauthorized(""))), + None => future::ready(Err(UnauthorizedError)), } } } + +#[derive(Debug)] +pub struct UnauthorizedError; + +impl Display for UnauthorizedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Unauthorized access") + } +} + +impl actix_web::ResponseError for UnauthorizedError { + fn status_code(&self) -> actix_web::http::StatusCode { + actix_web::http::StatusCode::UNAUTHORIZED + } + + fn error_response(&self) -> actix_web::HttpResponse { + actix_web::HttpResponse::Unauthorized().finish() + } +} diff --git a/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs b/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs index a1415f8..020bd0c 100644 --- a/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs +++ b/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs @@ -1,7 +1,10 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{auth_controller::AuthController, user_response_dto::UserResponseDto}, + application::error::auth_error::AuthError, framework::web::auth_middleware::UserId, }; @@ -26,7 +29,10 @@ pub async fn get_logged_in_user_handler( match result { Ok(user) => HttpResponse::Ok().json(user), Err(e) => { - log::error!("{e:?}"); + match e { + AuthError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/auth/src/framework/web/oidc_callback_handler.rs b/backend/feature/auth/src/framework/web/oidc_callback_handler.rs index a6494c7..cafda4b 100644 --- a/backend/feature/auth/src/framework/web/oidc_callback_handler.rs +++ b/backend/feature/auth/src/framework/web/oidc_callback_handler.rs @@ -1,5 +1,7 @@ use actix_session::Session; use actix_web::{HttpResponse, Responder, http::header, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{ @@ -48,7 +50,7 @@ pub async fn oidc_callback_handler( match result { Ok(user) => { if let Err(e) = session.insert::(SESSION_KEY_USER_ID, user.id) { - log::error!("{e:?}"); + capture_anyhow(&e.into()); return HttpResponse::InternalServerError().finish(); } HttpResponse::Found() @@ -61,7 +63,10 @@ pub async fn oidc_callback_handler( | AuthError::InvalidNonce | AuthError::InvalidState => HttpResponse::BadRequest().finish(), _ => { - log::error!("{e:?}"); + match e { + AuthError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/auth/src/framework/web/oidc_login_handler.rs b/backend/feature/auth/src/framework/web/oidc_login_handler.rs index b804676..864b03f 100644 --- a/backend/feature/auth/src/framework/web/oidc_login_handler.rs +++ b/backend/feature/auth/src/framework/web/oidc_login_handler.rs @@ -1,8 +1,11 @@ use actix_session::Session; use actix_web::{HttpResponse, Responder, http::header, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::auth_controller::AuthController, + application::error::auth_error::AuthError, framework::web::constants::{SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE}, }; @@ -24,11 +27,11 @@ pub async fn oidc_login_handler( match result { Ok(auth_url) => { if let Err(e) = session.insert::(SESSION_KEY_AUTH_STATE, auth_url.state) { - log::error!("{e:?}"); + capture_anyhow(&e.into()); return HttpResponse::InternalServerError().finish(); } if let Err(e) = session.insert::(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { - log::error!("{e:?}"); + capture_anyhow(&e.into()); return HttpResponse::InternalServerError().finish(); } HttpResponse::Found() @@ -36,7 +39,10 @@ pub async fn oidc_login_handler( .finish() } Err(e) => { - log::error!("{e:?}"); + match e { + AuthError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/image/Cargo.toml b/backend/feature/image/Cargo.toml index 36aee66..0e1d8dd 100644 --- a/backend/feature/image/Cargo.toml +++ b/backend/feature/image/Cargo.toml @@ -6,9 +6,11 @@ edition.workspace = true [dependencies] actix-multipart.workspace = true actix-web.workspace = true +anyhow.workspace = true async-trait.workspace = true futures.workspace = true log.workspace = true +sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true diff --git a/backend/feature/image/src/adapter/delivery/image_controller.rs b/backend/feature/image/src/adapter/delivery/image_controller.rs index 4f85440..4dd333f 100644 --- a/backend/feature/image/src/adapter/delivery/image_controller.rs +++ b/backend/feature/image/src/adapter/delivery/image_controller.rs @@ -57,7 +57,7 @@ impl ImageController for ImageControllerImpl { image: ImageRequestDto, ) -> Result { if !self.mime_type_whitelist.contains(&image.mime_type) { - return Err(ImageError::UnsupportedMimeType); + return Err(ImageError::UnsupportedMimeType(image.mime_type)); } let mime_type = image.mime_type.clone(); diff --git a/backend/feature/image/src/application/error/image_error.rs b/backend/feature/image/src/application/error/image_error.rs index 5c10b00..0d046b2 100644 --- a/backend/feature/image/src/application/error/image_error.rs +++ b/backend/feature/image/src/application/error/image_error.rs @@ -1,7 +1,18 @@ -#[derive(Debug, PartialEq)] +use std::fmt::Display; + +#[derive(Debug)] pub enum ImageError { - DatabaseError(String), - StorageError(String), NotFound, - UnsupportedMimeType, + UnsupportedMimeType(String), + Unexpected(anyhow::Error), +} + +impl Display for ImageError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ImageError::NotFound => write!(f, "Image not found"), + ImageError::UnsupportedMimeType(mime) => write!(f, "Unsupported MIME type: {}", mime), + ImageError::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } } diff --git a/backend/feature/image/src/framework/db/image_db_service_impl.rs b/backend/feature/image/src/framework/db/image_db_service_impl.rs index e61cc4c..37fa99f 100644 --- a/backend/feature/image/src/framework/db/image_db_service_impl.rs +++ b/backend/feature/image/src/framework/db/image_db_service_impl.rs @@ -34,7 +34,7 @@ impl ImageDbService for ImageDbServiceImpl { match id { Ok(id) => Ok(id), - Err(e) => Err(ImageError::DatabaseError(e.to_string())), + Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), } } @@ -59,7 +59,7 @@ impl ImageDbService for ImageDbServiceImpl { }), None => Err(ImageError::NotFound), }, - Err(e) => Err(ImageError::DatabaseError(e.to_string())), + Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), } } } diff --git a/backend/feature/image/src/framework/storage/image_storage_impl.rs b/backend/feature/image/src/framework/storage/image_storage_impl.rs index eb7b61a..62b5ac1 100644 --- a/backend/feature/image/src/framework/storage/image_storage_impl.rs +++ b/backend/feature/image/src/framework/storage/image_storage_impl.rs @@ -22,20 +22,19 @@ impl ImageStorageImpl { impl ImageStorage for ImageStorageImpl { fn write_data(&self, id: i32, data: &[u8]) -> Result<(), ImageError> { let dir_path = format!("{}/images", self.sotrage_path); - fs::create_dir_all(&dir_path).map_err(|e| ImageError::StorageError(e.to_string()))?; + fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(e.into()))?; let file_path = format!("{}/{}", dir_path, id); - let mut file = - File::create(&file_path).map_err(|e| ImageError::StorageError(e.to_string()))?; + let mut file = File::create(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; file.write_all(data) - .map_err(|e| ImageError::StorageError(e.to_string()))?; + .map_err(|e| ImageError::Unexpected(e.into()))?; Ok(()) } fn read_data(&self, id: i32) -> Result, ImageError> { let file_path = format!("{}/images/{}", self.sotrage_path, id); - let data = fs::read(&file_path).map_err(|e| ImageError::StorageError(e.to_string()))?; + let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; Ok(data) } } diff --git a/backend/feature/image/src/framework/web/get_image_by_id_handler.rs b/backend/feature/image/src/framework/web/get_image_by_id_handler.rs index 0fd65c9..e345c8c 100644 --- a/backend/feature/image/src/framework/web/get_image_by_id_handler.rs +++ b/backend/feature/image/src/framework/web/get_image_by_id_handler.rs @@ -1,4 +1,6 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use utoipa::ToSchema; use crate::{ @@ -29,8 +31,12 @@ pub async fn get_image_by_id_handler( .body(image_response.data), Err(e) => match e { ImageError::NotFound => HttpResponse::NotFound().finish(), + ImageError::Unexpected(e) => { + capture_anyhow(&e); + HttpResponse::InternalServerError().finish() + } _ => { - log::error!("{e:?}"); + capture_anyhow(&anyhow!(e)); HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/image/src/framework/web/upload_image_handler.rs b/backend/feature/image/src/framework/web/upload_image_handler.rs index 67fc86e..3964a8f 100644 --- a/backend/feature/image/src/framework/web/upload_image_handler.rs +++ b/backend/feature/image/src/framework/web/upload_image_handler.rs @@ -1,7 +1,9 @@ use actix_multipart::Multipart; use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; use auth::framework::web::auth_middleware::UserId; use futures::StreamExt; +use sentry::integrations::anyhow::capture_anyhow; use utoipa::ToSchema; use crate::{ @@ -73,9 +75,15 @@ pub async fn upload_image_handler( match result { Ok(image_info) => HttpResponse::Created().json(image_info), Err(e) => match e { - ImageError::UnsupportedMimeType => HttpResponse::BadRequest().body(format!("{e:?}")), + ImageError::UnsupportedMimeType(mime_type) => { + HttpResponse::BadRequest().body(format!("Unsupported MIME type: {}", mime_type)) + } + ImageError::Unexpected(e) => { + capture_anyhow(&e); + HttpResponse::InternalServerError().finish() + } _ => { - log::error!("{e:?}"); + capture_anyhow(&anyhow!(e)); HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/post/Cargo.toml b/backend/feature/post/Cargo.toml index 73d6eff..f7789c5 100644 --- a/backend/feature/post/Cargo.toml +++ b/backend/feature/post/Cargo.toml @@ -5,9 +5,11 @@ edition.workspace = true [dependencies] actix-web.workspace = true +anyhow.workspace = true async-trait.workspace = true chrono.workspace = true log.workspace = true +sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true diff --git a/backend/feature/post/src/application/error/post_error.rs b/backend/feature/post/src/application/error/post_error.rs index bcd0086..f917ee4 100644 --- a/backend/feature/post/src/application/error/post_error.rs +++ b/backend/feature/post/src/application/error/post_error.rs @@ -1,5 +1,16 @@ -#[derive(Debug, PartialEq)] +use std::fmt::Display; + +#[derive(Debug)] pub enum PostError { - DatabaseError(String), NotFound, + Unexpected(anyhow::Error), +} + +impl Display for PostError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PostError::NotFound => write!(f, "Post not found"), + PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } } diff --git a/backend/feature/post/src/framework/db/label_db_service_impl.rs b/backend/feature/post/src/framework/db/label_db_service_impl.rs index d08b750..ca7e1b1 100644 --- a/backend/feature/post/src/framework/db/label_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/label_db_service_impl.rs @@ -31,7 +31,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; Ok(id) } @@ -49,7 +49,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .execute(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))? + .map_err(|e| PostError::Unexpected(e.into()))? .rows_affected(); if affected_rows == 0 { @@ -71,7 +71,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -91,7 +91,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_all(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let mappers = records .into_iter() diff --git a/backend/feature/post/src/framework/db/post_db_service_impl.rs b/backend/feature/post/src/framework/db/post_db_service_impl.rs index 6fdcfba..1a60019 100644 --- a/backend/feature/post/src/framework/db/post_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/post_db_service_impl.rs @@ -64,7 +64,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let mut post_info_mappers_map = HashMap::::new(); @@ -136,7 +136,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; if records.is_empty() { return Err(PostError::NotFound); @@ -188,7 +188,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let post_id = sqlx::query_scalar!( r#" @@ -205,7 +205,7 @@ impl PostDbService for PostDbServiceImpl { ) .fetch_one(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -221,12 +221,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; } tx.commit() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; Ok(post_id) } @@ -236,7 +236,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let affected_rows = sqlx::query!( r#" @@ -258,7 +258,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))? + .map_err(|e| PostError::Unexpected(e.into()))? .rows_affected(); if affected_rows == 0 { @@ -274,7 +274,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -290,12 +290,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; } tx.commit() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; Ok(()) } diff --git a/backend/feature/post/src/framework/web/create_label_handler.rs b/backend/feature/post/src/framework/web/create_label_handler.rs index 499f7c0..4ee1aa7 100644 --- a/backend/feature/post/src/framework/web/create_label_handler.rs +++ b/backend/feature/post/src/framework/web/create_label_handler.rs @@ -1,9 +1,14 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto, - post_controller::PostController, +use crate::{ + adapter::delivery::{ + create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto, + post_controller::PostController, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -28,7 +33,10 @@ pub async fn create_label_handler( match result { Ok(label) => HttpResponse::Created().json(label), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/create_post_handler.rs b/backend/feature/post/src/framework/web/create_post_handler.rs index c26a6b9..a5c84c5 100644 --- a/backend/feature/post/src/framework/web/create_post_handler.rs +++ b/backend/feature/post/src/framework/web/create_post_handler.rs @@ -1,9 +1,14 @@ -use actix_web::{web, HttpResponse, Responder}; +use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - create_post_request_dto::CreatePostRequestDto, post_controller::PostController, - post_response_dto::PostResponseDto, +use crate::{ + adapter::delivery::{ + create_post_request_dto::CreatePostRequestDto, post_controller::PostController, + post_response_dto::PostResponseDto, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -28,7 +33,10 @@ pub async fn create_post_handler( match result { Ok(post) => HttpResponse::Created().json(post), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/get_all_labels_handler.rs b/backend/feature/post/src/framework/web/get_all_labels_handler.rs index 38edb70..bfc0934 100644 --- a/backend/feature/post/src/framework/web/get_all_labels_handler.rs +++ b/backend/feature/post/src/framework/web/get_all_labels_handler.rs @@ -1,7 +1,10 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - label_response_dto::LabelResponseDto, post_controller::PostController, +use crate::{ + adapter::delivery::{label_response_dto::LabelResponseDto, post_controller::PostController}, + application::error::post_error::PostError, }; #[utoipa::path( @@ -21,7 +24,10 @@ pub async fn get_all_labels_handler( match result { Ok(labels) => HttpResponse::Ok().json(labels), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/get_all_post_info_handler.rs b/backend/feature/post/src/framework/web/get_all_post_info_handler.rs index a47669d..2f3dd19 100644 --- a/backend/feature/post/src/framework/web/get_all_post_info_handler.rs +++ b/backend/feature/post/src/framework/web/get_all_post_info_handler.rs @@ -1,8 +1,13 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - post_controller::PostController, post_info_query_dto::PostQueryDto, - post_info_response_dto::PostInfoResponseDto, +use crate::{ + adapter::delivery::{ + post_controller::PostController, post_info_query_dto::PostQueryDto, + post_info_response_dto::PostInfoResponseDto, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -26,7 +31,10 @@ pub async fn get_all_post_info_handler( match result { Ok(post_info_list) => HttpResponse::Ok().json(post_info_list), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/get_post_by_id_handler.rs b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs index 03576c8..2057c5f 100644 --- a/backend/feature/post/src/framework/web/get_post_by_id_handler.rs +++ b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs @@ -1,4 +1,5 @@ use actix_web::{HttpResponse, Responder, web}; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{post_controller::PostController, post_response_dto::PostResponseDto}, @@ -24,13 +25,12 @@ pub async fn get_post_by_id_handler( match result { Ok(post) => HttpResponse::Ok().json(post), - Err(e) => { - if e == PostError::NotFound { - HttpResponse::NotFound().finish() - } else { - log::error!("{e:?}"); + Err(e) => match e { + PostError::NotFound => HttpResponse::NotFound().finish(), + PostError::Unexpected(e) => { + capture_anyhow(&e); HttpResponse::InternalServerError().finish() } - } + }, } } diff --git a/backend/feature/post/src/framework/web/update_label_handler.rs b/backend/feature/post/src/framework/web/update_label_handler.rs index 2d19746..5c2b32f 100644 --- a/backend/feature/post/src/framework/web/update_label_handler.rs +++ b/backend/feature/post/src/framework/web/update_label_handler.rs @@ -1,5 +1,6 @@ use actix_web::{HttpResponse, Responder, web}; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{ @@ -37,8 +38,8 @@ pub async fn update_label_handler( Ok(label) => HttpResponse::Ok().json(label), Err(e) => match e { PostError::NotFound => HttpResponse::NotFound().finish(), - _ => { - log::error!("{e:?}"); + PostError::Unexpected(e) => { + capture_anyhow(&e); HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/post/src/framework/web/update_post_handler.rs b/backend/feature/post/src/framework/web/update_post_handler.rs index d9cdb81..167b005 100644 --- a/backend/feature/post/src/framework/web/update_post_handler.rs +++ b/backend/feature/post/src/framework/web/update_post_handler.rs @@ -1,9 +1,13 @@ use actix_web::{HttpResponse, Responder, web}; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - post_controller::PostController, post_response_dto::PostResponseDto, - update_post_request_dto::UpdatePostRequestDto, +use crate::{ + adapter::delivery::{ + post_controller::PostController, post_response_dto::PostResponseDto, + update_post_request_dto::UpdatePostRequestDto, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -30,14 +34,12 @@ pub async fn update_post_handler( match result { Ok(post) => HttpResponse::Ok().json(post), - Err(e) => { - log::error!("{e:?}"); - match e { - crate::application::error::post_error::PostError::NotFound => { - HttpResponse::NotFound().finish() - } - _ => HttpResponse::InternalServerError().finish(), + Err(e) => match e { + PostError::NotFound => HttpResponse::NotFound().finish(), + PostError::Unexpected(e) => { + capture_anyhow(&e); + HttpResponse::InternalServerError().finish() } - } + }, } } diff --git a/backend/server/Cargo.toml b/backend/server/Cargo.toml index 8e5a7b1..03ed4c1 100644 --- a/backend/server/Cargo.toml +++ b/backend/server/Cargo.toml @@ -11,6 +11,7 @@ env_logger.workspace = true hex.workspace = true openidconnect.workspace = true percent-encoding.workspace = true +sentry.workspace = true sqlx.workspace = true utoipa.workspace = true utoipa-redoc.workspace = true diff --git a/backend/server/src/configuration.rs b/backend/server/src/configuration.rs index 3647d44..9959170 100644 --- a/backend/server/src/configuration.rs +++ b/backend/server/src/configuration.rs @@ -1,12 +1,13 @@ use openidconnect::reqwest; use crate::configuration::{ - db::DbConfiguration, oidc::OidcConfiguration, server::ServerConfiguration, - session::SessionConfiguration, storage::StorageConfiguration, + db::DbConfiguration, oidc::OidcConfiguration, sentry::SentryConfiguration, + server::ServerConfiguration, session::SessionConfiguration, storage::StorageConfiguration, }; pub mod db; pub mod oidc; +pub mod sentry; pub mod server; pub mod session; pub mod storage; @@ -15,6 +16,7 @@ pub mod storage; pub struct Configuration { pub db: DbConfiguration, pub oidc: OidcConfiguration, + pub sentry: SentryConfiguration, pub server: ServerConfiguration, pub session: SessionConfiguration, pub storage: StorageConfiguration, @@ -25,6 +27,7 @@ impl Configuration { Self { db: DbConfiguration::new(), oidc: OidcConfiguration::new(http_client).await, + sentry: SentryConfiguration::new(), server: ServerConfiguration::new(), session: SessionConfiguration::new(), storage: StorageConfiguration::new(), diff --git a/backend/server/src/configuration/sentry.rs b/backend/server/src/configuration/sentry.rs new file mode 100644 index 0000000..e9df39c --- /dev/null +++ b/backend/server/src/configuration/sentry.rs @@ -0,0 +1,22 @@ +#[derive(Clone)] +pub struct SentryConfiguration { + pub dsn: String, + pub options: sentry::ClientOptions, +} + +impl SentryConfiguration { + pub fn new() -> Self { + let dsn = std::env::var("SENTRY_DSN").unwrap_or("".to_string()); + + Self { + dsn: dsn, + options: sentry::ClientOptions { + release: sentry::release_name!(), + traces_sample_rate: 1.0, + send_default_pii: true, + max_request_body_size: sentry::MaxRequestBodySize::Always, + ..Default::default() + }, + } + } +} diff --git a/backend/server/src/main.rs b/backend/server/src/main.rs index cae50d6..0fd269e 100644 --- a/backend/server/src/main.rs +++ b/backend/server/src/main.rs @@ -5,17 +5,19 @@ use actix_web::{ App, Error, HttpServer, body::MessageBody, dev::{ServiceFactory, ServiceRequest, ServiceResponse}, + rt::Runtime, web, }; use auth::framework::web::auth_web_routes::configure_auth_routes; use image::framework::web::image_web_routes::configure_image_routes; use openidconnect::reqwest; use post::framework::web::post_web_routes::configure_post_routes; -use server::{api_doc::configure_api_doc_routes, configuration::Configuration, container::Container}; +use server::{ + api_doc::configure_api_doc_routes, configuration::Configuration, container::Container, +}; use sqlx::{Pool, Postgres}; -#[actix_web::main] -async fn main() -> std::io::Result<()> { +fn main() -> std::io::Result<()> { dotenv::dotenv().ok(); env_logger::init(); @@ -24,31 +26,45 @@ async fn main() -> std::io::Result<()> { .build() .expect("Failed to create HTTP client"); - let configuration = Configuration::new(http_client.clone()).await; + let rt = Runtime::new().unwrap(); + let configuration = rt.block_on(async { Configuration::new(http_client.clone()).await }); - let host = configuration.server.host.clone(); - let port = configuration.server.port; + let _guard = sentry::init(( + configuration.sentry.dsn.clone(), + configuration.sentry.options.clone(), + )); - let db_pool = configuration.db.create_connection().await; - let session_key = configuration.session.session_key.clone(); - let session_store = configuration.session.create_session_store().await; + actix_web::rt::System::new().block_on(async { + let host = configuration.server.host.clone(); + let port = configuration.server.port; - HttpServer::new(move || { - create_app( - db_pool.clone(), - http_client.clone(), - SessionMiddleware::builder(session_store.clone(), session_key.clone()), - configuration.clone(), - ) - }) - .bind((host, port))? - .run() - .await + let db_pool = configuration.db.create_connection().await; + let session_key = configuration.session.session_key.clone(); + let session_store = configuration.session.create_session_store().await; + + HttpServer::new(move || { + create_app( + db_pool.clone(), + http_client.clone(), + sentry::integrations::actix::Sentry::builder() + .capture_server_errors(true) + .start_transaction(true), + SessionMiddleware::builder(session_store.clone(), session_key.clone()), + configuration.clone(), + ) + }) + .bind((host, port))? + .run() + .await + })?; + + Ok(()) } fn create_app( db_pool: Pool, http_client: reqwest::Client, + sentry_builder: sentry::integrations::actix::SentryBuilder, session_middleware_builder: SessionMiddlewareBuilder, configuration: Configuration, ) -> App< @@ -64,6 +80,7 @@ fn create_app( App::new() // The middlewares are executed in opposite order as registration. + .wrap(sentry_builder.finish()) .wrap(session_middleware_builder.build()) .app_data(web::Data::from(container.auth_controller)) .app_data(web::Data::from(container.image_controller)) -- 2.47.2 From beffee8f8e216f5cdc8ddf82c8a69d01e5a2a201 Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Wed, 6 Aug 2025 18:17:44 +0800 Subject: [PATCH 3/4] BLOG-90 feat: add common feature and improve error handling across services --- backend/Cargo.lock | 10 ++++++++ backend/Cargo.toml | 13 ++++++++++- backend/feature/auth/Cargo.toml | 2 ++ .../src/framework/db/user_db_service_impl.rs | 7 +++--- .../framework/oidc/auth_oidc_service_impl.rs | 2 +- backend/feature/common/Cargo.toml | 7 ++++++ backend/feature/common/src/framework.rs | 1 + backend/feature/common/src/framework/error.rs | 21 +++++++++++++++++ backend/feature/common/src/lib.rs | 1 + backend/feature/image/Cargo.toml | 1 + .../src/framework/db/image_db_service_impl.rs | 5 ++-- .../framework/storage/image_storage_impl.rs | 9 +++++--- backend/feature/post/Cargo.toml | 1 + .../src/framework/db/label_db_service_impl.rs | 9 ++++---- .../src/framework/db/post_db_service_impl.rs | 23 ++++++++++--------- backend/server/src/configuration/sentry.rs | 1 + 16 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 backend/feature/common/Cargo.toml create mode 100644 backend/feature/common/src/framework.rs create mode 100644 backend/feature/common/src/framework/error.rs create mode 100644 backend/feature/common/src/lib.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index de4590d..0b6c222 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -426,6 +426,7 @@ dependencies = [ "actix-web", "anyhow", "async-trait", + "common", "log", "openidconnect", "sentry", @@ -619,6 +620,13 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "common" +version = "0.2.0" +dependencies = [ + "sqlx", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1743,6 +1751,7 @@ dependencies = [ "anyhow", "async-trait", "auth", + "common", "futures", "log", "sentry", @@ -2399,6 +2408,7 @@ dependencies = [ "async-trait", "auth", "chrono", + "common", "log", "sentry", "serde", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index fd697c9..8940323 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,11 +1,21 @@ [workspace] -members = ["server", "feature/auth", "feature/image", "feature/post"] +members = [ + "server", + "feature/auth", + "feature/common", + "feature/image", + "feature/post", + "feature/common", +] resolver = "2" [workspace.package] version = "0.2.0" edition = "2024" +[profile.release] +debug = true + [workspace.dependencies] actix-multipart = "0.7.2" actix-session = { version = "0.10.1", features = ["redis-session"] } @@ -41,5 +51,6 @@ utoipa-redoc = { version = "6.0.0", features = ["actix-web"] } server.path = "server" auth.path = "feature/auth" +common.path = "feature/common" image.path = "feature/image" post.path = "feature/post" diff --git a/backend/feature/auth/Cargo.toml b/backend/feature/auth/Cargo.toml index cd238bb..248b4ff 100644 --- a/backend/feature/auth/Cargo.toml +++ b/backend/feature/auth/Cargo.toml @@ -14,3 +14,5 @@ sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true + +common.workspace = true diff --git a/backend/feature/auth/src/framework/db/user_db_service_impl.rs b/backend/feature/auth/src/framework/db/user_db_service_impl.rs index d4d911d..f7752fe 100644 --- a/backend/feature/auth/src/framework/db/user_db_service_impl.rs +++ b/backend/feature/auth/src/framework/db/user_db_service_impl.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -31,7 +32,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::Unexpected(e.into()))?; + .map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -56,7 +57,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::Unexpected(e.into()))?; + .map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -78,7 +79,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|e| AuthError::Unexpected(e.into()))?; + .map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?; Ok(id) } diff --git a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs index 037fc0c..122ac04 100644 --- a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs +++ b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs @@ -80,7 +80,7 @@ impl AuthOidcService for AuthOidcServiceImpl { let token_response = self .oidc_client .exchange_code(AuthorizationCode::new(code.to_string())) - .map_err(|e| AuthError::Unexpected(e.into()))? + .unwrap() .request_async(&self.http_client) .await .map_err(|_| AuthError::InvalidAuthCode)?; diff --git a/backend/feature/common/Cargo.toml b/backend/feature/common/Cargo.toml new file mode 100644 index 0000000..dee37f8 --- /dev/null +++ b/backend/feature/common/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "common" +version.workspace = true +edition.workspace = true + +[dependencies] +sqlx.workspace = true diff --git a/backend/feature/common/src/framework.rs b/backend/feature/common/src/framework.rs new file mode 100644 index 0000000..a281f3e --- /dev/null +++ b/backend/feature/common/src/framework.rs @@ -0,0 +1 @@ +pub mod error; \ No newline at end of file diff --git a/backend/feature/common/src/framework/error.rs b/backend/feature/common/src/framework/error.rs new file mode 100644 index 0000000..b1b7f15 --- /dev/null +++ b/backend/feature/common/src/framework/error.rs @@ -0,0 +1,21 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct IOError(pub std::io::Error); + +#[derive(Debug)] +pub struct DatabaseError(pub sqlx::Error); + +impl std::error::Error for IOError {} +impl Display for IOError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for DatabaseError {} +impl Display for DatabaseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/backend/feature/common/src/lib.rs b/backend/feature/common/src/lib.rs new file mode 100644 index 0000000..fa86c78 --- /dev/null +++ b/backend/feature/common/src/lib.rs @@ -0,0 +1 @@ +pub mod framework; \ No newline at end of file diff --git a/backend/feature/image/Cargo.toml b/backend/feature/image/Cargo.toml index 0e1d8dd..1ec81b9 100644 --- a/backend/feature/image/Cargo.toml +++ b/backend/feature/image/Cargo.toml @@ -16,3 +16,4 @@ sqlx.workspace = true utoipa.workspace = true auth.workspace = true +common.workspace = true diff --git a/backend/feature/image/src/framework/db/image_db_service_impl.rs b/backend/feature/image/src/framework/db/image_db_service_impl.rs index 37fa99f..453c441 100644 --- a/backend/feature/image/src/framework/db/image_db_service_impl.rs +++ b/backend/feature/image/src/framework/db/image_db_service_impl.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -34,7 +35,7 @@ impl ImageDbService for ImageDbServiceImpl { match id { Ok(id) => Ok(id), - Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), + Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())), } } @@ -59,7 +60,7 @@ impl ImageDbService for ImageDbServiceImpl { }), None => Err(ImageError::NotFound), }, - Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), + Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())), } } } diff --git a/backend/feature/image/src/framework/storage/image_storage_impl.rs b/backend/feature/image/src/framework/storage/image_storage_impl.rs index 62b5ac1..cf6a5a7 100644 --- a/backend/feature/image/src/framework/storage/image_storage_impl.rs +++ b/backend/feature/image/src/framework/storage/image_storage_impl.rs @@ -3,6 +3,8 @@ use std::{ io::Write, }; +use common::framework::error::IOError; + use crate::{ adapter::gateway::image_storage::ImageStorage, application::error::image_error::ImageError, }; @@ -22,10 +24,11 @@ impl ImageStorageImpl { impl ImageStorage for ImageStorageImpl { fn write_data(&self, id: i32, data: &[u8]) -> Result<(), ImageError> { let dir_path = format!("{}/images", self.sotrage_path); - fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(e.into()))?; + fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?; let file_path = format!("{}/{}", dir_path, id); - let mut file = File::create(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; + let mut file = + File::create(&file_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?; file.write_all(data) .map_err(|e| ImageError::Unexpected(e.into()))?; @@ -34,7 +37,7 @@ impl ImageStorage for ImageStorageImpl { fn read_data(&self, id: i32) -> Result, ImageError> { let file_path = format!("{}/images/{}", self.sotrage_path, id); - let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; + let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?; Ok(data) } } diff --git a/backend/feature/post/Cargo.toml b/backend/feature/post/Cargo.toml index f7789c5..2903619 100644 --- a/backend/feature/post/Cargo.toml +++ b/backend/feature/post/Cargo.toml @@ -15,3 +15,4 @@ sqlx.workspace = true utoipa.workspace = true auth.workspace = true +common.workspace = true diff --git a/backend/feature/post/src/framework/db/label_db_service_impl.rs b/backend/feature/post/src/framework/db/label_db_service_impl.rs index ca7e1b1..f189fd1 100644 --- a/backend/feature/post/src/framework/db/label_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/label_db_service_impl.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -31,7 +32,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; Ok(id) } @@ -49,7 +50,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .execute(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))? + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))? .rows_affected(); if affected_rows == 0 { @@ -71,7 +72,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -91,7 +92,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_all(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let mappers = records .into_iter() diff --git a/backend/feature/post/src/framework/db/post_db_service_impl.rs b/backend/feature/post/src/framework/db/post_db_service_impl.rs index 1a60019..94d2de8 100644 --- a/backend/feature/post/src/framework/db/post_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/post_db_service_impl.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -64,7 +65,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let mut post_info_mappers_map = HashMap::::new(); @@ -136,7 +137,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; if records.is_empty() { return Err(PostError::NotFound); @@ -188,7 +189,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let post_id = sqlx::query_scalar!( r#" @@ -205,7 +206,7 @@ impl PostDbService for PostDbServiceImpl { ) .fetch_one(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -221,12 +222,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; } tx.commit() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; Ok(post_id) } @@ -236,7 +237,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let affected_rows = sqlx::query!( r#" @@ -258,7 +259,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))? + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))? .rows_affected(); if affected_rows == 0 { @@ -274,7 +275,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -290,12 +291,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; } tx.commit() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; Ok(()) } diff --git a/backend/server/src/configuration/sentry.rs b/backend/server/src/configuration/sentry.rs index e9df39c..ec4bcbe 100644 --- a/backend/server/src/configuration/sentry.rs +++ b/backend/server/src/configuration/sentry.rs @@ -15,6 +15,7 @@ impl SentryConfiguration { traces_sample_rate: 1.0, send_default_pii: true, max_request_body_size: sentry::MaxRequestBodySize::Always, + attach_stacktrace: true, ..Default::default() }, } -- 2.47.2 From 5f1f5f43ae47e270f0f2d68d4b6b2d994412b81d Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Wed, 6 Aug 2025 19:24:31 +0800 Subject: [PATCH 4/4] BLOG-90 refactor: remove 'log' dependency from multiple Cargo.toml files --- backend/Cargo.lock | 3 --- backend/feature/auth/Cargo.toml | 1 - backend/feature/image/Cargo.toml | 1 - backend/feature/post/Cargo.toml | 1 - 4 files changed, 6 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 0b6c222..035c424 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -427,7 +427,6 @@ dependencies = [ "anyhow", "async-trait", "common", - "log", "openidconnect", "sentry", "serde", @@ -1753,7 +1752,6 @@ dependencies = [ "auth", "common", "futures", - "log", "sentry", "serde", "sqlx", @@ -2409,7 +2407,6 @@ dependencies = [ "auth", "chrono", "common", - "log", "sentry", "serde", "sqlx", diff --git a/backend/feature/auth/Cargo.toml b/backend/feature/auth/Cargo.toml index 248b4ff..a7ab651 100644 --- a/backend/feature/auth/Cargo.toml +++ b/backend/feature/auth/Cargo.toml @@ -8,7 +8,6 @@ actix-session.workspace = true actix-web.workspace = true anyhow.workspace = true async-trait.workspace = true -log.workspace = true openidconnect.workspace = true sentry.workspace = true serde.workspace = true diff --git a/backend/feature/image/Cargo.toml b/backend/feature/image/Cargo.toml index 1ec81b9..64ba461 100644 --- a/backend/feature/image/Cargo.toml +++ b/backend/feature/image/Cargo.toml @@ -9,7 +9,6 @@ actix-web.workspace = true anyhow.workspace = true async-trait.workspace = true futures.workspace = true -log.workspace = true sentry.workspace = true serde.workspace = true sqlx.workspace = true diff --git a/backend/feature/post/Cargo.toml b/backend/feature/post/Cargo.toml index 2903619..afd48c7 100644 --- a/backend/feature/post/Cargo.toml +++ b/backend/feature/post/Cargo.toml @@ -8,7 +8,6 @@ actix-web.workspace = true anyhow.workspace = true async-trait.workspace = true chrono.workspace = true -log.workspace = true sentry.workspace = true serde.workspace = true sqlx.workspace = true -- 2.47.2