From 9aab34cae7d83ab9e5a6f0ba3b1cd9a7a6f1e6d1 Mon Sep 17 00:00:00 2001 From: Adedayo-Data Date: Mon, 23 Feb 2026 20:50:37 +0100 Subject: [PATCH 1/2] Feat: Implement production level error monitoring with sentry capturing runtime errors and contextualizing unhandled exception. --- backend/package-lock.json | 942 ++++++++++++++++++++++++ backend/package.json | 8 +- backend/src/index.js | 125 ++-- backend/src/services/indexingService.js | 421 ++++++----- 4 files changed, 1244 insertions(+), 252 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index ea70a180..d2fec7a3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@apollo/server": "^4.9.5", "@graphql-tools/schema": "^10.0.2", + "@sentry/node": "^10.39.0", + "@sentry/profiling-node": "^10.39.0", "axios": "^1.6.2", "cors": "^2.8.5", "discord.js": "^14.14.1", @@ -94,6 +96,46 @@ "openapi-types": ">=7" } }, + "node_modules/@apm-js-collab/code-transformer": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz", + "integrity": "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==", + "license": "Apache-2.0" + }, + "node_modules/@apm-js-collab/tracing-hooks": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.1.tgz", + "integrity": "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==", + "license": "Apache-2.0", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.8.0", + "debug": "^4.4.1", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@apm-js-collab/tracing-hooks/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@apm-js-collab/tracing-hooks/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/@apollo/cache-control-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", @@ -1500,6 +1542,560 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "license": "MIT" }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz", + "integrity": "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", + "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.58.0.tgz", + "integrity": "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.54.0.tgz", + "integrity": "sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.28.0.tgz", + "integrity": "sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.59.0.tgz", + "integrity": "sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.30.0.tgz", + "integrity": "sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.54.0.tgz", + "integrity": "sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.58.0.tgz", + "integrity": "sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.57.0.tgz", + "integrity": "sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.211.0.tgz", + "integrity": "sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.59.0.tgz", + "integrity": "sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.20.0.tgz", + "integrity": "sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.55.0.tgz", + "integrity": "sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.59.0.tgz", + "integrity": "sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.55.0.tgz", + "integrity": "sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.64.0.tgz", + "integrity": "sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.57.0.tgz", + "integrity": "sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.57.0.tgz", + "integrity": "sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.57.0.tgz", + "integrity": "sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.63.0.tgz", + "integrity": "sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.59.0.tgz", + "integrity": "sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.30.0.tgz", + "integrity": "sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.21.0.tgz", + "integrity": "sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", + "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", + "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz", + "integrity": "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", + "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.2.0.tgz", + "integrity": "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.207.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz", + "integrity": "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz", + "integrity": "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.207.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1669,6 +2265,194 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@sentry-internal/node-cpu-profiler": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/node-cpu-profiler/-/node-cpu-profiler-2.2.0.tgz", + "integrity": "sha512-oLHVYurqZfADPh5hvmQYS5qx8t0UZzT2u6+/68VXsFruQEOnYJTODKgU3BVLmemRs3WE6kCJjPeFdHVYOQGSzQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "node-abi": "^3.73.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/core": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.39.0.tgz", + "integrity": "sha512-xCLip2mBwCdRrvXHtVEULX0NffUTYZZBhEUGht0WFL+GNdNQ7gmBOGOczhZlrf2hgFFtDO0fs1xiP9bqq5orEQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.39.0.tgz", + "integrity": "sha512-dx66DtU/xkCTPEDsjU+mYSIEbzu06pzKNQcDA2wvx7wvwsUciZ5yA32Ce/o6p2uHHgy0/joJX9rP5J/BIijaOA==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^2.5.0", + "@opentelemetry/core": "^2.5.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation-amqplib": "0.58.0", + "@opentelemetry/instrumentation-connect": "0.54.0", + "@opentelemetry/instrumentation-dataloader": "0.28.0", + "@opentelemetry/instrumentation-express": "0.59.0", + "@opentelemetry/instrumentation-fs": "0.30.0", + "@opentelemetry/instrumentation-generic-pool": "0.54.0", + "@opentelemetry/instrumentation-graphql": "0.58.0", + "@opentelemetry/instrumentation-hapi": "0.57.0", + "@opentelemetry/instrumentation-http": "0.211.0", + "@opentelemetry/instrumentation-ioredis": "0.59.0", + "@opentelemetry/instrumentation-kafkajs": "0.20.0", + "@opentelemetry/instrumentation-knex": "0.55.0", + "@opentelemetry/instrumentation-koa": "0.59.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.55.0", + "@opentelemetry/instrumentation-mongodb": "0.64.0", + "@opentelemetry/instrumentation-mongoose": "0.57.0", + "@opentelemetry/instrumentation-mysql": "0.57.0", + "@opentelemetry/instrumentation-mysql2": "0.57.0", + "@opentelemetry/instrumentation-pg": "0.63.0", + "@opentelemetry/instrumentation-redis": "0.59.0", + "@opentelemetry/instrumentation-tedious": "0.30.0", + "@opentelemetry/instrumentation-undici": "0.21.0", + "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/semantic-conventions": "^1.39.0", + "@prisma/instrumentation": "7.2.0", + "@sentry/core": "10.39.0", + "@sentry/node-core": "10.39.0", + "@sentry/opentelemetry": "10.39.0", + "import-in-the-middle": "^2.0.6", + "minimatch": "^9.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.39.0.tgz", + "integrity": "sha512-xdeBG00TmtAcGvXnZNbqOCvnZ5kY3s5aT/L8wUQ0w0TT2KmrC9XL/7UHUfJ45TLbjl10kZOtaMQXgUjpwSJW+g==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/tracing-hooks": "^0.3.1", + "@sentry/core": "10.39.0", + "@sentry/opentelemetry": "10.39.0", + "import-in-the-middle": "^2.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/resources": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/context-async-hooks": { + "optional": true + }, + "@opentelemetry/core": { + "optional": true + }, + "@opentelemetry/instrumentation": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/semantic-conventions": { + "optional": true + } + } + }, + "node_modules/@sentry/node/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@sentry/node/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@sentry/node/node_modules/minimatch": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.39.0.tgz", + "integrity": "sha512-eU8t/pyxjy7xYt6PNCVxT+8SJw5E3pnupdcUNN4ClqG4O5lX4QCDLtId48ki7i30VqrLtR7vmCHMSvqXXdvXPA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.39.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + } + }, + "node_modules/@sentry/profiling-node": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/profiling-node/-/profiling-node-10.39.0.tgz", + "integrity": "sha512-S+YHtTyGIhUFg/qYsrJ6KYHkZRqKSNx4JDWIZsn1/w6+87NfijDT/pVj+mdUHV6Z2imj+/HyAURANHgaZJKy4w==", + "license": "MIT", + "dependencies": { + "@sentry-internal/node-cpu-profiler": "^2.2.0", + "@sentry/core": "10.39.0", + "@sentry/node": "10.39.0" + }, + "bin": { + "sentry-prune-profiler-binaries": "scripts/prune-profiler-binaries.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -1860,6 +2644,15 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "25.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", @@ -1879,6 +2672,26 @@ "form-data": "^4.0.4" } }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -1928,6 +2741,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/validator": { "version": "13.15.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", @@ -2004,6 +2826,27 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2818,6 +3661,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3312,6 +4164,12 @@ "node": ">= 0.6" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -3644,6 +4502,24 @@ "dev": true, "license": "ISC" }, + "node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/import-in-the-middle/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -4879,6 +5755,12 @@ "node": "*" } }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -4922,6 +5804,30 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -5655,6 +6561,42 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/require-in-the-middle/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", diff --git a/backend/package.json b/backend/package.json index 7216b2d7..09a53e13 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,8 +13,11 @@ "dependencies": { "@apollo/server": "^4.9.5", "@graphql-tools/schema": "^10.0.2", + "@sentry/node": "^10.39.0", + "@sentry/profiling-node": "^10.39.0", "axios": "^1.6.2", "cors": "^2.8.5", + "discord.js": "^14.14.1", "dotenv": "^16.3.1", "express": "^4.18.2", "express-rate-limit": "^7.1.5", @@ -22,12 +25,11 @@ "graphql-subscriptions": "^2.0.0", "graphql-ws": "^5.14.3", "pg": "^8.11.3", + "redis": "^4.6.12", "sequelize": "^6.35.2", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", - "ws": "^8.14.2", - "discord.js": "^14.14.1", - "redis": "^4.6.12" + "ws": "^8.14.2" }, "devDependencies": { "jest": "^29.7.0", diff --git a/backend/src/index.js b/backend/src/index.js index d4cddade..9d4db7ee 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -6,6 +6,9 @@ const dotenv = require('dotenv'); const http = require('http'); const checkApiKey = require('./middleware/checkApiKey'); +const Sentry = require('@sentry/node'); +const { nodeProfilingIntegration } = require('@sentry/profiling-node'); + // Import swagger documentation const swaggerUi = require('swagger-ui-express'); const swaggerSpecs = require('./swagger/options'); @@ -13,11 +16,28 @@ const swaggerSpecs = require('./swagger/options'); dotenv.config(); const app = express(); + +Sentry.init({ + // Fallback to a dummy DSN so Sentry SDK doesn't disable itself when testing without credentials + dsn: process.env.SENTRY_DSN || 'http://public_key@localhost:9999/1', + debug: true, // Output sentry operations to console (disable in production) + environment: process.env.NODE_ENV || 'development', + integrations: [ + nodeProfilingIntegration(), + ], + tracesSampleRate: 1.0, // 100% of transactions for performance monitoring + profilesSampleRate: 1.0, // 100% of transactions are profiled +}); + const PORT = process.env.PORT || 3000; // Create HTTP server for GraphQL subscriptions const httpServer = http.createServer(app); +// Sentry request handler must be the first middleware +app.use(Sentry.Handlers.requestHandler()); +app.use(Sentry.Handlers.tracingHandler()); + // Middleware app.use(cors()); app.use(express.json()); @@ -70,9 +90,9 @@ app.post('/api/claims', async (req, res) => { res.status(201).json({ success: true, data: claim }); } catch (error) { console.error('Error processing claim:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -83,9 +103,9 @@ app.post('/api/claims/batch', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error processing batch claims:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -93,15 +113,15 @@ app.post('/api/claims/batch', async (req, res) => { app.post('/api/claims/backfill-prices', async (req, res) => { try { const processedCount = await indexingService.backfillMissingPrices(); - res.json({ - success: true, - message: `Backfilled prices for ${processedCount} claims` + res.json({ + success: true, + message: `Backfilled prices for ${processedCount} claims` }); } catch (error) { console.error('Error backfilling prices:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -110,19 +130,19 @@ app.get('/api/claims/:userAddress/realized-gains', async (req, res) => { try { const { userAddress } = req.params; const { startDate, endDate } = req.query; - + const gains = await indexingService.getRealizedGains( - userAddress, + userAddress, startDate ? new Date(startDate) : null, endDate ? new Date(endDate) : null ); - + res.json({ success: true, data: gains }); } catch (error) { console.error('Error calculating realized gains:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -137,9 +157,9 @@ app.post('/api/admin/revoke', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error revoking access:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -151,9 +171,9 @@ app.post('/api/admin/create', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error creating vault:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -165,9 +185,9 @@ app.post('/api/admin/transfer', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error transferring vault:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -179,9 +199,9 @@ app.get('/api/admin/audit-logs', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error fetching audit logs:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -194,9 +214,9 @@ app.post('/api/admin/propose-new-admin', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error proposing new admin:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -208,9 +228,9 @@ app.post('/api/admin/accept-ownership', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error accepting ownership:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -222,9 +242,9 @@ app.post('/api/admin/transfer-ownership', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error transferring ownership:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -236,9 +256,9 @@ app.get('/api/admin/pending-transfers', async (req, res) => { res.json({ success: true, data: result }); } catch (error) { console.error('Error fetching pending transfers:', error); - res.status(500).json({ - success: false, - error: error.message + res.status(500).json({ + success: false, + error: error.message }); } }); @@ -268,16 +288,16 @@ app.get('/api/stats/tvl', async (req, res) => { app.get('/api/vault/:id/export', rateLimitExport, async (req, res) => { try { const { id } = req.params; - + // Set response headers for CSV download res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', `attachment; filename="vault-${id}-export-${new Date().toISOString().split('T')[0]}.csv"`); - + // Stream the CSV data await vaultExportService.streamVaultAsCSV(id, res); } catch (error) { console.error('Error exporting vault:', error); - + // If headers haven't been sent yet, send JSON error response if (!res.headersSent) { res.status(500).json({ @@ -291,15 +311,18 @@ app.get('/api/vault/:id/export', rateLimitExport, async (req, res) => { } }); +// Sentry error handler must be before any other error middleware and after all controllers +app.use(Sentry.Handlers.errorHandler()); + // Start server const startServer = async () => { try { await sequelize.authenticate(); console.log('Database connection established successfully.'); - + await sequelize.sync(); console.log('Database synchronized successfully.'); - + // Initialize Redis Cache try { await cacheService.connect(); @@ -312,7 +335,7 @@ const startServer = async () => { console.error('Failed to connect to Redis:', cacheError); console.log('Continuing without Redis cache...'); } - + // Initialize GraphQL Server let graphQLServer = null; try { @@ -320,7 +343,7 @@ const startServer = async () => { const { createGraphQLServer } = require('./graphql/server'); graphQLServer = await createGraphQLServer(app); console.log('GraphQL Server initialized successfully.'); - + const serverInfo = graphQLServer.getServerInfo(); console.log(`GraphQL Playground available at: ${serverInfo.playgroundUrl}`); console.log(`GraphQL Subscriptions available at: ${serverInfo.subscriptionEndpoint}`); @@ -328,7 +351,7 @@ const startServer = async () => { console.error('Failed to initialize GraphQL Server:', graphqlError); console.log('Continuing with REST API only...'); } - + // Initialize Discord Bot try { await discordBotService.start(); @@ -336,7 +359,7 @@ const startServer = async () => { console.error('Failed to initialize Discord Bot:', discordError); console.log('Continuing without Discord bot...'); } - + // Start the HTTP server httpServer.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); diff --git a/backend/src/services/indexingService.js b/backend/src/services/indexingService.js index 2801cd69..83676f9a 100644 --- a/backend/src/services/indexingService.js +++ b/backend/src/services/indexingService.js @@ -2,6 +2,7 @@ const { ClaimsHistory, Vault, SubSchedule } = require('../models'); const priceService = require('./priceService'); const slackWebhookService = require('./slackWebhookService'); const tvlService = require('./tvlService'); +const Sentry = require('@sentry/node'); const EventEmitter = require('events'); const claimEventEmitter = new EventEmitter(); @@ -36,7 +37,7 @@ class IndexingService { }); console.log(`Processed claim ${transaction_hash} with price $${price_at_claim_usd}`); - + // Check for large claim and send Slack alert try { await slackWebhookService.processClaimAlert(claim.toJSON()); @@ -44,7 +45,7 @@ class IndexingService { console.error('Error processing claim alert:', alertError); // Don't throw - alert failure shouldn't fail the claim processing } - + // Update TVL for claim event try { await tvlService.handleClaim(claim.toJSON()); @@ -52,7 +53,7 @@ class IndexingService { console.error('Error updating TVL for claim:', tvlError); // Don't throw - TVL update failure shouldn't fail claim processing } - + // Emit internal claim event for WebSocket gateway claimEventEmitter.emit('claim', claim.toJSON()); @@ -76,247 +77,271 @@ class IndexingService { console.warn('Webhook not fired: admin_address does not match organization_id'); } } - return claim; - } catch (error) { - console.error('Error processing claim:', error); - throw error; } + return claim; + } catch(error) { + console.error('Error processing claim:', error); + Sentry.captureException(error, { + tags: { operation: 'processClaim' }, + extra: { claimData } + }); + throw error; } +} async processBatchClaims(claimsData) { - const results = []; - const errors = []; - - for (const claimData of claimsData) { - try { - const result = await this.processClaim(claimData); - results.push(result); - } catch (error) { - errors.push({ - transaction_hash: claimData.transaction_hash, - error: error.message - }); - } - } + const results = []; + const errors = []; - return { - processed: results.length, - errors: errors.length, - results, - errors - }; - } - - async backfillMissingPrices() { + for (const claimData of claimsData) { try { - // Find all claims without price data - const claimsWithoutPrice = await ClaimsHistory.findAll({ - where: { - price_at_claim_usd: null - }, - order: [['claim_timestamp', 'ASC']], - limit: 100 // Process in batches to avoid rate limits - }); - - console.log(`Found ${claimsWithoutPrice.length} claims without price data`); - - for (const claim of claimsWithoutPrice) { - try { - const price = await priceService.getTokenPrice( - claim.token_address, - claim.claim_timestamp - ); - - await claim.update({ price_at_claim_usd: price }); - console.log(`Backfilled price for claim ${claim.transaction_hash}: $${price}`); - } catch (error) { - console.error(`Failed to backfill price for claim ${claim.transaction_hash}:`, error.message); - } - } - - return claimsWithoutPrice.length; + const result = await this.processClaim(claimData); + results.push(result); } catch (error) { - console.error('Error in backfillMissingPrices:', error); - throw error; + Sentry.captureException(error, { + tags: { operation: 'processBatchClaims_individual' }, + extra: { transaction_hash: claimData.transaction_hash } + }); + errors.push({ + transaction_hash: claimData.transaction_hash, + error: error.message + }); } } - async getRealizedGains(userAddress, startDate = null, endDate = null) { - try { - const whereClause = { - user_address: userAddress, - price_at_claim_usd: { - [require('sequelize').Op.ne]: null - } - }; + return { + processed: results.length, + errors: errors.length, + results, + errors + }; +} - if (startDate) { - whereClause.claim_timestamp = { - [require('sequelize').Op.gte]: startDate - }; - } + async backfillMissingPrices() { + try { + // Find all claims without price data + const claimsWithoutPrice = await ClaimsHistory.findAll({ + where: { + price_at_claim_usd: null + }, + order: [['claim_timestamp', 'ASC']], + limit: 100 // Process in batches to avoid rate limits + }); + + console.log(`Found ${claimsWithoutPrice.length} claims without price data`); + + for (const claim of claimsWithoutPrice) { + try { + const price = await priceService.getTokenPrice( + claim.token_address, + claim.claim_timestamp + ); - if (endDate) { - whereClause.claim_timestamp = { - ...whereClause.claim_timestamp, - [require('sequelize').Op.lte]: endDate - }; + await claim.update({ price_at_claim_usd: price }); + console.log(`Backfilled price for claim ${claim.transaction_hash}: $${price}`); + } catch (error) { + console.error(`Failed to backfill price for claim ${claim.transaction_hash}:`, error.message); } + } - const claims = await ClaimsHistory.findAll({ - where: whereClause, - order: [['claim_timestamp', 'ASC']] - }); - - let totalRealizedGains = 0; + return claimsWithoutPrice.length; + } catch (error) { + console.error('Error in backfillMissingPrices:', error); + Sentry.captureException(error, { + tags: { operation: 'backfillMissingPrices' } + }); + throw error; + } +} - for (const claim of claims) { - const realizedGain = parseFloat(claim.amount_claimed) * parseFloat(claim.price_at_claim_usd); - totalRealizedGains += realizedGain; + async getRealizedGains(userAddress, startDate = null, endDate = null) { + try { + const whereClause = { + user_address: userAddress, + price_at_claim_usd: { + [require('sequelize').Op.ne]: null } + }; - return { - user_address: userAddress, - total_realized_gains_usd: totalRealizedGains, - claims_processed: claims.length, - period: { - start_date: startDate, - end_date: endDate - } + if (startDate) { + whereClause.claim_timestamp = { + [require('sequelize').Op.gte]: startDate }; - } catch (error) { - console.error('Error calculating realized gains:', error); - throw error; } - } - async processTopUpEvent(topUpData) { - try { - const { - vault_address, - top_up_amount, - transaction_hash, - block_number, - timestamp, - cliff_duration = null, - vesting_duration - } = topUpData; + if (endDate) { + whereClause.claim_timestamp = { + ...whereClause.claim_timestamp, + [require('sequelize').Op.lte]: endDate + }; + } - const vault = await Vault.findOne({ - where: { vault_address, is_active: true } - }); + const claims = await ClaimsHistory.findAll({ + where: whereClause, + order: [['claim_timestamp', 'ASC']] + }); - if (!vault) { - throw new Error(`Vault ${vault_address} not found or inactive`); - } + let totalRealizedGains = 0; - const topUpTimestamp = new Date(timestamp); - let cliffDate = null; - let vestingStartDate = topUpTimestamp; + for (const claim of claims) { + const realizedGain = parseFloat(claim.amount_claimed) * parseFloat(claim.price_at_claim_usd); + totalRealizedGains += realizedGain; + } - if (cliff_duration && cliff_duration > 0) { - cliffDate = new Date(topUpTimestamp.getTime() + cliff_duration * 1000); - vestingStartDate = cliffDate; + return { + user_address: userAddress, + total_realized_gains_usd: totalRealizedGains, + claims_processed: claims.length, + period: { + start_date: startDate, + end_date: endDate } + }; + } catch (error) { + console.error('Error calculating realized gains:', error); + Sentry.captureException(error, { + tags: { operation: 'getRealizedGains' }, + extra: { userAddress, startDate, endDate } + }); + throw error; + } +} - const subSchedule = await SubSchedule.create({ - vault_id: vault.id, - top_up_amount, - top_up_transaction_hash: transaction_hash, - top_up_timestamp: topUpTimestamp, - cliff_duration, - cliff_date: cliffDate, - vesting_start_date: vestingStartDate, - vesting_duration, - }); + async processTopUpEvent(topUpData) { + try { + const { + vault_address, + top_up_amount, + transaction_hash, + block_number, + timestamp, + cliff_duration = null, + vesting_duration + } = topUpData; + + const vault = await Vault.findOne({ + where: { vault_address, is_active: true } + }); + + if (!vault) { + throw new Error(`Vault ${vault_address} not found or inactive`); + } - await vault.update({ - total_amount: parseFloat(vault.total_amount) + parseFloat(top_up_amount), - }); + const topUpTimestamp = new Date(timestamp); + let cliffDate = null; + let vestingStartDate = topUpTimestamp; - console.log(`Processed top-up ${transaction_hash} for vault ${vault_address}`); - return subSchedule; - } catch (error) { - console.error('Error processing top-up event:', error); - throw error; + if (cliff_duration && cliff_duration > 0) { + cliffDate = new Date(topUpTimestamp.getTime() + cliff_duration * 1000); + vestingStartDate = cliffDate; } + + const subSchedule = await SubSchedule.create({ + vault_id: vault.id, + top_up_amount, + top_up_transaction_hash: transaction_hash, + top_up_timestamp: topUpTimestamp, + cliff_duration, + cliff_date: cliffDate, + vesting_start_date: vestingStartDate, + vesting_duration, + }); + + await vault.update({ + total_amount: parseFloat(vault.total_amount) + parseFloat(top_up_amount), + }); + + console.log(`Processed top-up ${transaction_hash} for vault ${vault_address}`); + return subSchedule; + } catch (error) { + console.error('Error processing top-up event:', error); + Sentry.captureException(error, { + tags: { operation: 'processTopUpEvent' }, + extra: { topUpData } + }); + throw error; } +} async processReleaseEvent(releaseData) { - try { - const { - vault_address, - user_address, - amount_released, - transaction_hash, - block_number, - timestamp - } = releaseData; - - const vault = await Vault.findOne({ - where: { vault_address, is_active: true }, - include: [{ - model: SubSchedule, - as: 'subSchedules', - where: { is_active: true }, - required: false, - }], - }); - - if (!vault) { - throw new Error(`Vault ${vault_address} not found or inactive`); - } - - let remainingToRelease = parseFloat(amount_released); + try { + const { + vault_address, + user_address, + amount_released, + transaction_hash, + block_number, + timestamp + } = releaseData; + + const vault = await Vault.findOne({ + where: { vault_address, is_active: true }, + include: [{ + model: SubSchedule, + as: 'subSchedules', + where: { is_active: true }, + required: false, + }], + }); + + if (!vault) { + throw new Error(`Vault ${vault_address} not found or inactive`); + } - for (const subSchedule of vault.subSchedules) { - if (remainingToRelease <= 0) break; + let remainingToRelease = parseFloat(amount_released); - const releasable = this.calculateSubScheduleReleasable(subSchedule, new Date(timestamp)); - if (releasable <= 0) continue; + for (const subSchedule of vault.subSchedules) { + if (remainingToRelease <= 0) break; - const releaseFromThis = Math.min(remainingToRelease, releasable); - - await subSchedule.update({ - amount_released: parseFloat(subSchedule.amount_released) + releaseFromThis, - }); + const releasable = this.calculateSubScheduleReleasable(subSchedule, new Date(timestamp)); + if (releasable <= 0) continue; - remainingToRelease -= releaseFromThis; - } + const releaseFromThis = Math.min(remainingToRelease, releasable); - if (remainingToRelease > 0) { - throw new Error(`Insufficient releasable amount. Remaining: ${remainingToRelease}`); - } + await subSchedule.update({ + amount_released: parseFloat(subSchedule.amount_released) + releaseFromThis, + }); - console.log(`Processed release ${transaction_hash} for vault ${vault_address}, amount: ${amount_released}`); - return { success: true, amount_released }; - } catch (error) { - console.error('Error processing release event:', error); - throw error; + remainingToRelease -= releaseFromThis; } - } - calculateSubScheduleReleasable(subSchedule, asOfDate = new Date()) { - if (subSchedule.cliff_date && asOfDate < subSchedule.cliff_date) { - return 0; + if (remainingToRelease > 0) { + throw new Error(`Insufficient releasable amount. Remaining: ${remainingToRelease}`); } - if (asOfDate < subSchedule.vesting_start_date) { - return 0; - } + console.log(`Processed release ${transaction_hash} for vault ${vault_address}, amount: ${amount_released}`); + return { success: true, amount_released }; + } catch (error) { + console.error('Error processing release event:', error); + Sentry.captureException(error, { + tags: { operation: 'processReleaseEvent' }, + extra: { releaseData } + }); + throw error; + } +} - const vestingEnd = new Date(subSchedule.vesting_start_date.getTime() + subSchedule.vesting_duration * 1000); - if (asOfDate >= vestingEnd) { - return parseFloat(subSchedule.top_up_amount) - parseFloat(subSchedule.amount_released); - } +calculateSubScheduleReleasable(subSchedule, asOfDate = new Date()) { + if (subSchedule.cliff_date && asOfDate < subSchedule.cliff_date) { + return 0; + } - const vestedTime = asOfDate - subSchedule.vesting_start_date; - const vestedRatio = vestedTime / (subSchedule.vesting_duration * 1000); - const totalVested = parseFloat(subSchedule.top_up_amount) * vestedRatio; - const releasable = totalVested - parseFloat(subSchedule.amount_released); + if (asOfDate < subSchedule.vesting_start_date) { + return 0; + } - return Math.max(0, releasable); + const vestingEnd = new Date(subSchedule.vesting_start_date.getTime() + subSchedule.vesting_duration * 1000); + if (asOfDate >= vestingEnd) { + return parseFloat(subSchedule.top_up_amount) - parseFloat(subSchedule.amount_released); } + + const vestedTime = asOfDate - subSchedule.vesting_start_date; + const vestedRatio = vestedTime / (subSchedule.vesting_duration * 1000); + const totalVested = parseFloat(subSchedule.top_up_amount) * vestedRatio; + const releasable = totalVested - parseFloat(subSchedule.amount_released); + + return Math.max(0, releasable); +} } module.exports = { From a2764feb81526b4e8ca7fd5c471c053d1b205429 Mon Sep 17 00:00:00 2001 From: Adedayo-Data Date: Mon, 23 Feb 2026 20:51:17 +0100 Subject: [PATCH 2/2] Add tests to Sentry Error Monitoring integration --- backend/test-sentry-local.js | 50 ++++++++++++++++++++++++++++++++++ backend/test-sentry-startup.js | 35 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 backend/test-sentry-local.js create mode 100644 backend/test-sentry-startup.js diff --git a/backend/test-sentry-local.js b/backend/test-sentry-local.js new file mode 100644 index 00000000..545ffcf5 --- /dev/null +++ b/backend/test-sentry-local.js @@ -0,0 +1,50 @@ +const Sentry = require('@sentry/node'); + +// 1. Initialize Sentry with a dummy DSN and debug mode +Sentry.init({ + dsn: 'http://dummy_key@localhost:9999/1', + debug: true, + environment: 'local-test', +}); + +// 2. Mock database requires to prevent hanging on connection attempts +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function (request) { + if (request.includes('../models') || request.includes('./models')) { + return { + ClaimsHistory: {}, Vault: {}, SubSchedule: {}, + OrganizationWebhook: {}, sequelize: { Op: {} } + }; + } + if (request.includes('./database/connection')) { + return { sequelize: { authenticate: async () => { }, sync: async () => { } } }; + } + return originalRequire.apply(this, arguments); +}; + +// 3. Require the instrumented service +const { instance: indexingService } = require('./src/services/indexingService'); + +async function testSentry() { + console.log('\n============================================='); + console.log('🚀 Triggering indexingService error... '); + console.log('=============================================\n'); + + try { + // Calling processClaim with null forces a destructuring TypeError + await indexingService.processClaim(null); + } catch (error) { + console.log('\n============================================='); + console.log('🛑 Error locally caught. Watch Sentry debug logs:'); + console.log('=============================================\n'); + + // Allow Sentry background queues to flush its debug logs + setTimeout(() => { + console.log('✅ Test complete.'); + process.exit(0); + }, 2000); + } +} + +testSentry(); diff --git a/backend/test-sentry-startup.js b/backend/test-sentry-startup.js new file mode 100644 index 00000000..56e1ef50 --- /dev/null +++ b/backend/test-sentry-startup.js @@ -0,0 +1,35 @@ +require('dotenv').config(); +const { spawn } = require('child_process'); + +console.log('Testing Sentry integration startup...'); +const serverProcess = spawn('node', ['src/index.js']); + +let started = false; + +serverProcess.stdout.on('data', (data) => { + const output = data.toString(); + console.log(`STDOUT: ${output}`); + if (output.includes('Server is running on port') || output.includes('Database connection established')) { + started = true; + console.log('✅ Server started successfully with Sentry integrated.'); + serverProcess.kill(); + process.exit(0); + } +}); + +serverProcess.stderr.on('data', (data) => { + console.error(`STDERR: ${data.toString()}`); + if (data.toString().includes('Sentry') || data.toString().includes('Error')) { + console.error('❌ Sentry startup error detected.'); + serverProcess.kill(); + process.exit(1); + } +}); + +setTimeout(() => { + if (!started) { + console.log('⏳ Server took too long to start or no success message seen. Exiting.'); + serverProcess.kill(); + process.exit(1); + } +}, 10000);