From 3d09f16a99946c38fc32e20d48143cadffa713e5 Mon Sep 17 00:00:00 2001
From: "ohamamarachi474@gmail.com"
Date: Wed, 27 May 2026 16:36:43 +0100
Subject: [PATCH 1/2] feat: implement multi-source crypto news ingestion
service and initialize backend authentication and frontend infrastructure
modules
---
apps/backend/package-lock.json | 456 +++---------------
apps/backend/src/auth/auth.module.ts | 4 +-
apps/backend/src/auth/auth.service.ts | 58 +++
apps/backend/src/stellar/stellar.service.ts | 7 +-
.../src/users/dto/link-stellar-account.dto.ts | 8 +
apps/backend/src/users/users.module.ts | 9 +-
apps/backend/src/users/users.service.ts | 11 +
.../src/ingestion/news_fetcher.py | 201 ++++----
.../src/ingestion/social_fetcher.py | 9 +-
apps/data-processing/src/utils/translator.py | 88 ++++
apps/webapp/app/community/page.tsx | 2 +-
apps/webapp/app/dashboard/page.tsx | 186 +++++--
apps/webapp/app/providers.tsx | 9 +-
apps/webapp/components/crypto-table.tsx | 4 +-
.../components/onboarding/OnboardingModal.tsx | 82 +++-
apps/webapp/components/transaction-detail.tsx | 2 +-
apps/webapp/lib/api-services.ts | 46 ++
17 files changed, 630 insertions(+), 552 deletions(-)
create mode 100644 apps/data-processing/src/utils/translator.py
diff --git a/apps/backend/package-lock.json b/apps/backend/package-lock.json
index 66e1ed6a..947df848 100644
--- a/apps/backend/package-lock.json
+++ b/apps/backend/package-lock.json
@@ -1728,7 +1728,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
@@ -1741,7 +1741,7 @@
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
@@ -2569,6 +2569,7 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
@@ -2586,6 +2587,7 @@
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -2598,6 +2600,7 @@
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -2610,12 +2613,14 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
@@ -2633,6 +2638,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
@@ -2648,6 +2654,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
@@ -3104,7 +3111,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -3125,7 +3132,7 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
@@ -4043,6 +4050,7 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
"license": "MIT",
"optional": true,
"engines": {
@@ -4868,6 +4876,7 @@
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
"integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@stellar/js-xdr": {
@@ -4952,28 +4961,28 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/babel__core": {
@@ -5966,7 +5975,7 @@
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
@@ -5979,7 +5988,7 @@
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -5992,6 +6001,7 @@
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -6132,6 +6142,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
"integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==",
+ "dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@@ -6168,6 +6179,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz",
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
@@ -6183,7 +6195,7 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/argparse": {
@@ -6468,6 +6480,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/bare-addon-resolve": {
@@ -7266,6 +7279,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
@@ -7561,7 +7575,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/cron": {
@@ -7597,6 +7611,7 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -7674,6 +7689,7 @@
"version": "1.11.20",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -7706,6 +7722,7 @@
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
"integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"babel-plugin-macros": "^3.1.0"
@@ -7841,7 +7858,7 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
- "devOptional": true,
+ "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
@@ -7921,6 +7938,7 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/ecc-jsbn": {
@@ -8188,14 +8206,6 @@
"node": ">= 0.4"
}
},
- "node_modules/es-module-lexer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
- "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -8256,6 +8266,7 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -8794,6 +8805,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
@@ -8807,6 +8819,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
@@ -9089,6 +9102,7 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
@@ -9565,31 +9579,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/har-schema": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
- "license": "ISC",
- "peer": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/har-validator": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
- "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
- "deprecated": "this library is no longer supported",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "ajv": "^6.12.3",
- "har-schema": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -10454,6 +10443,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
"license": "ISC"
},
"node_modules/isstream": {
@@ -10556,6 +10546,7 @@
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
@@ -11279,6 +11270,7 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
@@ -11639,7 +11631,7 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "devOptional": true,
+ "dev": true,
"license": "ISC"
},
"node_modules/makeerror": {
@@ -11814,6 +11806,7 @@
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -12138,16 +12131,6 @@
"node": ">=8"
}
},
- "node_modules/oauth-sign": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
- "license": "Apache-2.0",
- "peer": true,
- "engines": {
- "node": "*"
- }
- },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -12372,6 +12355,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/parent-module": {
@@ -12489,6 +12473,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -12505,6 +12490,7 @@
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
@@ -12521,6 +12507,7 @@
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
"license": "ISC"
},
"node_modules/path-to-regexp": {
@@ -13394,39 +13381,6 @@
"node": ">=0.10"
}
},
- "node_modules/request": {
- "version": "2.88.2",
- "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
- "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
- "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "aws-sign2": "~0.7.0",
- "aws4": "^1.8.0",
- "caseless": "~0.12.0",
- "combined-stream": "~1.0.6",
- "extend": "~3.0.2",
- "forever-agent": "~0.6.1",
- "form-data": "~2.3.2",
- "har-validator": "~5.1.3",
- "http-signature": "~1.2.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.19",
- "oauth-sign": "~0.9.0",
- "performance-now": "^2.1.0",
- "qs": "~6.5.2",
- "safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
- "tunnel-agent": "^0.6.0",
- "uuid": "^3.3.2"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/request-promise-core": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
@@ -13442,111 +13396,6 @@
"request": "^2.34"
}
},
- "node_modules/request/node_modules/form-data": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
- "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 0.12"
- }
- },
- "node_modules/request/node_modules/http-signature": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
- "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "assert-plus": "^1.0.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- },
- "engines": {
- "node": ">=0.8",
- "npm": ">=1.3.7"
- }
- },
- "node_modules/request/node_modules/jsprim": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
- "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "assert-plus": "1.0.0",
- "extsprintf": "1.3.0",
- "json-schema": "0.4.0",
- "verror": "1.10.0"
- },
- "engines": {
- "node": ">=0.6.0"
- }
- },
- "node_modules/request/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/request/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/request/node_modules/qs": {
- "version": "6.5.5",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz",
- "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==",
- "license": "BSD-3-Clause",
- "peer": true,
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/request/node_modules/tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "license": "BSD-3-Clause",
- "peer": true,
- "dependencies": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/request/node_modules/uuid": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
- "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
- "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
- "license": "MIT",
- "peer": true,
- "bin": {
- "uuid": "bin/uuid"
- }
- },
"node_modules/require-addon": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
@@ -13991,6 +13840,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -14003,6 +13853,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -14084,6 +13935,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@@ -14287,6 +14139,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz",
"integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==",
+ "dev": true,
"funding": [
"https://github.com/scriptcoded/sql-highlight?sponsor=1",
{
@@ -14480,6 +14333,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -14563,6 +14417,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -15210,7 +15065,7 @@
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
@@ -15254,7 +15109,7 @@
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -15461,6 +15316,7 @@
"version": "0.3.28",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz",
"integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@sqltools/formatter": "^1.2.5",
@@ -15563,6 +15419,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -15572,6 +15429,7 @@
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -15585,6 +15443,7 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -15605,6 +15464,7 @@
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
@@ -15620,7 +15480,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -15770,6 +15630,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
@@ -15823,7 +15684,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/v8-to-istanbul": {
@@ -15913,56 +15774,6 @@
"defaults": "^1.0.3"
}
},
- "node_modules/webpack": {
- "version": "5.105.4",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz",
- "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/eslint-scope": "^3.7.7",
- "@types/estree": "^1.0.8",
- "@types/json-schema": "^7.0.15",
- "@webassemblyjs/ast": "^1.14.1",
- "@webassemblyjs/wasm-edit": "^1.14.1",
- "@webassemblyjs/wasm-parser": "^1.14.1",
- "acorn": "^8.16.0",
- "acorn-import-phases": "^1.0.3",
- "browserslist": "^4.28.1",
- "chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.20.0",
- "es-module-lexer": "^2.0.0",
- "eslint-scope": "5.1.1",
- "events": "^3.2.0",
- "glob-to-regexp": "^0.4.1",
- "graceful-fs": "^4.2.11",
- "json-parse-even-better-errors": "^2.3.1",
- "loader-runner": "^4.3.1",
- "mime-types": "^2.1.27",
- "neo-async": "^2.6.2",
- "schema-utils": "^4.3.3",
- "tapable": "^2.3.0",
- "terser-webpack-plugin": "^5.3.17",
- "watchpack": "^2.5.1",
- "webpack-sources": "^3.3.4"
- },
- "bin": {
- "webpack": "bin/webpack.js"
- },
- "engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependenciesMeta": {
- "webpack-cli": {
- "optional": true
- }
- }
- },
"node_modules/webpack-node-externals": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz",
@@ -15983,150 +15794,11 @@
"node": ">=10.13.0"
}
},
- "node_modules/webpack/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==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/webpack/node_modules/acorn-import-phases": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
- "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10.13.0"
- },
- "peerDependencies": {
- "acorn": "^8.14.0"
- }
- },
- "node_modules/webpack/node_modules/ajv": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
- "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/webpack/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/webpack/node_modules/eslint-scope": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
- "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^4.1.1"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/webpack/node_modules/estraverse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/webpack/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/webpack/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/webpack/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/webpack/node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -16280,6 +15952,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@@ -16362,6 +16035,7 @@
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
@@ -16378,6 +16052,7 @@
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
@@ -16396,6 +16071,7 @@
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
@@ -16405,7 +16081,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
diff --git a/apps/backend/src/auth/auth.module.ts b/apps/backend/src/auth/auth.module.ts
index 1893574c..6d9fe017 100644
--- a/apps/backend/src/auth/auth.module.ts
+++ b/apps/backend/src/auth/auth.module.ts
@@ -1,4 +1,4 @@
-import { Module } from '@nestjs/common';
+import { Module, forwardRef } from '@nestjs/common';
import { JwtModule, JwtModuleOptions } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
@@ -16,7 +16,7 @@ import { RefreshToken } from './entities/refresh-token.entity';
@Module({
imports: [
TypeOrmModule.forFeature([User, PasswordResetToken, RefreshToken]),
- UsersModule,
+ forwardRef(() => UsersModule),
EmailModule,
PassportModule,
JwtModule.registerAsync({
diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts
index f7ec87e7..ce9e2b98 100644
--- a/apps/backend/src/auth/auth.service.ts
+++ b/apps/backend/src/auth/auth.service.ts
@@ -193,6 +193,64 @@ export class AuthService {
};
}
+ /**
+ * Verify the signed challenge without creating a user session or issuing JWT.
+ * Used for secure account linking.
+ */
+ async verifyChallengeOnly(
+ publicKey: string,
+ signedChallenge: string,
+ ): Promise {
+ await Promise.resolve();
+ const storedChallenge = this.challengeStore.get(publicKey);
+
+ if (!storedChallenge) {
+ throw new BadRequestException(
+ 'No challenge found for this public key. Please request a new challenge.',
+ );
+ }
+
+ if (Date.now() > storedChallenge.expiresAt) {
+ this.challengeStore.delete(publicKey);
+ throw new BadRequestException(
+ 'Challenge has expired. Please request a new challenge.',
+ );
+ }
+
+ const networkPassphrase =
+ this.stellarNetwork === 'testnet' ? Networks.TESTNET : Networks.PUBLIC;
+
+ let transaction: Transaction;
+
+ try {
+ transaction = new Transaction(signedChallenge, networkPassphrase);
+ } catch {
+ this.challengeStore.delete(publicKey);
+ throw new BadRequestException('Invalid transaction format');
+ }
+
+ // Verify the transaction was signed by the user
+ const userSignature = transaction.signatures.find((sig) => {
+ try {
+ const keypair = Keypair.fromPublicKey(publicKey);
+ return keypair.verify(transaction.hash(), sig.signature());
+ } catch {
+ return false;
+ }
+ });
+
+ if (!userSignature) {
+ this.challengeStore.delete(publicKey);
+ throw new BadRequestException(
+ 'Invalid signature. Transaction was not signed by the provided public key.',
+ );
+ }
+
+ // Remove used challenge
+ this.challengeStore.delete(publicKey);
+ return true;
+ }
+
/**
* Verify the signed challenge and issue a JWT
*/
diff --git a/apps/backend/src/stellar/stellar.service.ts b/apps/backend/src/stellar/stellar.service.ts
index b9d3736d..0c1b36c1 100644
--- a/apps/backend/src/stellar/stellar.service.ts
+++ b/apps/backend/src/stellar/stellar.service.ts
@@ -160,16 +160,15 @@ export class StellarService {
): Promise {
validateStellarPublicKey(publicKey);
this.logger.debug(`Fetching transactions for account: ${publicKey}`);
-
try {
- const payments = await this.server
- .payments()
+ const operations = await this.server
+ .operations()
.forAccount(publicKey)
.order('desc')
.limit(limit)
.call();
- return payments.records;
+ return operations.records;
} catch (error: unknown) {
this.logger.error(`Error fetching transactions for ${publicKey}:`, error);
throw new HorizonUnavailableException(
diff --git a/apps/backend/src/users/dto/link-stellar-account.dto.ts b/apps/backend/src/users/dto/link-stellar-account.dto.ts
index 1e98063f..3565745f 100644
--- a/apps/backend/src/users/dto/link-stellar-account.dto.ts
+++ b/apps/backend/src/users/dto/link-stellar-account.dto.ts
@@ -22,4 +22,12 @@ export class LinkStellarAccountDto {
@IsOptional()
@MaxLength(100)
label?: string;
+
+ @ApiProperty({
+ description:
+ 'Signed challenge transaction XDR proving ownership of public key',
+ example: 'AAAAA...',
+ })
+ @IsString()
+ signedChallenge: string;
}
diff --git a/apps/backend/src/users/users.module.ts b/apps/backend/src/users/users.module.ts
index 3c462d16..9439d8dc 100644
--- a/apps/backend/src/users/users.module.ts
+++ b/apps/backend/src/users/users.module.ts
@@ -1,4 +1,4 @@
-import { Module } from '@nestjs/common';
+import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@@ -6,9 +6,14 @@ import { User } from './entities/user.entity';
import { StellarAccount } from './entities/stellar-account.entity';
import { StellarService } from '../stellar/stellar.service';
import { UploadModule } from '../upload/upload.module';
+import { AuthModule } from '../auth/auth.module';
@Module({
- imports: [TypeOrmModule.forFeature([User, StellarAccount]), UploadModule],
+ imports: [
+ TypeOrmModule.forFeature([User, StellarAccount]),
+ UploadModule,
+ forwardRef(() => AuthModule),
+ ],
providers: [UsersService, StellarService],
controllers: [UsersController],
exports: [UsersService],
diff --git a/apps/backend/src/users/users.service.ts b/apps/backend/src/users/users.service.ts
index 418adfb2..4b05ba13 100644
--- a/apps/backend/src/users/users.service.ts
+++ b/apps/backend/src/users/users.service.ts
@@ -4,6 +4,8 @@ import {
BadRequestException,
ConflictException,
Logger,
+ Inject,
+ forwardRef,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@@ -14,6 +16,7 @@ import { LinkStellarAccountDto } from './dto/link-stellar-account.dto';
import { StellarAccountResponseDto } from './dto/stellar-account-response.dto';
import { UpdateStellarAccountLabelDto } from './dto/update-stellar-account-label.dto';
import { UploadService } from '../upload/upload.service';
+import { AuthService } from '../auth/auth.service';
import crypto from 'crypto';
@Injectable()
@@ -27,6 +30,8 @@ export class UsersService {
private stellarAccountRepository: Repository,
private stellarService: StellarService,
private uploadService: UploadService,
+ @Inject(forwardRef(() => AuthService))
+ private authService: AuthService,
) {}
// --- BASIC CRUD ---
@@ -78,6 +83,12 @@ export class UsersService {
userId: string,
dto: LinkStellarAccountDto,
): Promise {
+ // Verify ownership via challenge-response signature first
+ await this.authService.verifyChallengeOnly(
+ dto.publicKey,
+ dto.signedChallenge,
+ );
+
this.stellarService.validatePublicKeyOrThrow(dto.publicKey);
const user = await this.usersRepository.findOne({ where: { id: userId } });
diff --git a/apps/data-processing/src/ingestion/news_fetcher.py b/apps/data-processing/src/ingestion/news_fetcher.py
index 0d3721bd..12715c8f 100644
--- a/apps/data-processing/src/ingestion/news_fetcher.py
+++ b/apps/data-processing/src/ingestion/news_fetcher.py
@@ -10,6 +10,7 @@
from dataclasses import dataclass, asdict
from .news_deduplicator import NewsDeduplicator
from datetime import datetime
+from src.utils.translator import translate_and_normalize
import requests
from requests.exceptions import RequestException, Timeout
@@ -115,64 +116,65 @@ def _fetch_cryptocompare(self, limit: int) -> List[NewsArticle]:
articles = []
try:
- self._respect_rate_limit()
-
- params = {
- "lang": "EN",
- "categories": "BTC,ETH,BLOCKCHAIN",
- "excludeCategories": "Sponsored",
- }
-
headers = {"Authorization": f"Apikey {self.cryptocompare_key}"}
- response = self.session.get(
- APIConfig.CRYPTOCOMPARE_URL,
- params=params,
- headers=headers,
- timeout=APIConfig.TIMEOUT,
- )
-
- if response.status_code != 200:
- self._handle_api_error(response, "CryptoCompare")
+ for lang in ["EN", "ES", "PT"]:
+ self._respect_rate_limit()
- data = response.json()
+ params = {
+ "lang": lang,
+ "categories": "BTC,ETH,BLOCKCHAIN",
+ "excludeCategories": "Sponsored",
+ }
- if data.get("Type") != 100:
- raise ValueError(
- f"CryptoCompare API returned error: {data.get('Message', 'Unknown error')}"
+ response = self.session.get(
+ APIConfig.CRYPTOCOMPARE_URL,
+ params=params,
+ headers=headers,
+ timeout=APIConfig.TIMEOUT,
)
- # Parse articles
- for item in data.get("Data", [])[:limit]:
- try:
- article = NewsArticle(
- id=f"cc_{item['id']}",
- title=item.get("title", ""),
- content=item.get("body", ""),
- summary=item.get("short_description", ""),
- source=item.get("source", "Unknown"),
- url=item.get("url", ""),
- published_at=datetime.fromtimestamp(
- item.get("published_on", 0)
- ),
- categories=(
- item.get("categories", "").split("|")
- if item.get("categories")
- else []
- ),
- tags=(
- item.get("tags", "").split("|") if item.get("tags") else []
- ),
- )
+ if response.status_code != 200:
+ self._handle_api_error(response, "CryptoCompare")
+
+ data = response.json()
- # Avoid duplicates
- if article.id not in self.seen_articles:
- articles.append(article)
- self.seen_articles.add(article.id)
+ if data.get("Type") != 100:
+ raise ValueError(
+ f"CryptoCompare API returned error: {data.get('Message', 'Unknown error')}"
+ )
- except KeyError as e:
- print(f"Warning: Missing key in CryptoCompare data: {e}")
- continue
+ # Parse articles
+ for item in data.get("Data", [])[:limit]:
+ try:
+ article = NewsArticle(
+ id=f"cc_{item['id']}",
+ title=translate_and_normalize(item.get("title", "")),
+ content=translate_and_normalize(item.get("body", "")),
+ summary=translate_and_normalize(item.get("short_description", "")),
+ source=item.get("source", "Unknown"),
+ url=item.get("url", ""),
+ published_at=datetime.fromtimestamp(
+ item.get("published_on", 0)
+ ),
+ categories=(
+ item.get("categories", "").split("|")
+ if item.get("categories")
+ else []
+ ),
+ tags=(
+ item.get("tags", "").split("|") if item.get("tags") else []
+ ),
+ )
+
+ # Avoid duplicates
+ if article.id not in self.seen_articles:
+ articles.append(article)
+ self.seen_articles.add(article.id)
+
+ except KeyError as e:
+ print(f"Warning: Missing key in CryptoCompare data: {e}")
+ continue
except RequestException as e:
print(f"Error fetching from CryptoCompare: {e}")
@@ -186,60 +188,61 @@ def _fetch_newsapi(self, limit: int) -> List[NewsArticle]:
articles = []
try:
- self._respect_rate_limit()
-
# Calculate date range (last 7 days for recent news)
to_date = datetime.now()
from_date = datetime.fromtimestamp(to_date.timestamp() - (7 * 24 * 3600))
- params = {
- "q": "cryptocurrency OR blockchain OR bitcoin OR ethereum",
- "language": "en",
- "sortBy": "publishedAt",
- "pageSize": min(limit, 100), # NewsAPI max is 100
- "from": from_date.strftime("%Y-%m-%d"),
- "to": to_date.strftime("%Y-%m-%d"),
- "apiKey": self.newsapi_key,
- }
-
- response = self.session.get(
- APIConfig.NEWSAPI_URL, params=params, timeout=APIConfig.TIMEOUT
- )
-
- if response.status_code != 200:
- self._handle_api_error(response, "NewsAPI")
-
- data = response.json()
-
- # Parse articles
- for item in data.get("articles", [])[:limit]:
- try:
- published_at = datetime.fromisoformat(
- item["publishedAt"].replace("Z", "+00:00")
- )
-
- article = NewsArticle(
- id=f"na_{hash(item['url']) & 0xFFFFFFFF}",
- title=item.get("title", ""),
- content=item.get("content", ""),
- summary=item.get("description", ""),
- source=item.get("source", {}).get("name", "Unknown"),
- url=item.get("url", ""),
- published_at=published_at,
- categories=[
- "crypto",
- "blockchain",
- ], # NewsAPI doesn't provide categories
- )
-
- # Avoid duplicates
- if article.id not in self.seen_articles:
- articles.append(article)
- self.seen_articles.add(article.id)
+ for lang in ["en", "es", "pt"]:
+ self._respect_rate_limit()
+
+ params = {
+ "q": "cryptocurrency OR blockchain OR bitcoin OR ethereum",
+ "language": lang,
+ "sortBy": "publishedAt",
+ "pageSize": min(limit, 100), # NewsAPI max is 100
+ "from": from_date.strftime("%Y-%m-%d"),
+ "to": to_date.strftime("%Y-%m-%d"),
+ "apiKey": self.newsapi_key,
+ }
+
+ response = self.session.get(
+ APIConfig.NEWSAPI_URL, params=params, timeout=APIConfig.TIMEOUT
+ )
- except (KeyError, ValueError) as e:
- print(f"Warning: Error parsing NewsAPI article: {e}")
- continue
+ if response.status_code != 200:
+ self._handle_api_error(response, "NewsAPI")
+
+ data = response.json()
+
+ # Parse articles
+ for item in data.get("articles", [])[:limit]:
+ try:
+ published_at = datetime.fromisoformat(
+ item["publishedAt"].replace("Z", "+00:00")
+ )
+
+ article = NewsArticle(
+ id=f"na_{hash(item['url']) & 0xFFFFFFFF}",
+ title=translate_and_normalize(item.get("title", "")),
+ content=translate_and_normalize(item.get("content", "")),
+ summary=translate_and_normalize(item.get("description", "")),
+ source=item.get("source", {}).get("name", "Unknown"),
+ url=item.get("url", ""),
+ published_at=published_at,
+ categories=[
+ "crypto",
+ "blockchain",
+ ], # NewsAPI doesn't provide categories
+ )
+
+ # Avoid duplicates
+ if article.id not in self.seen_articles:
+ articles.append(article)
+ self.seen_articles.add(article.id)
+
+ except (KeyError, ValueError) as e:
+ print(f"Warning: Error parsing NewsAPI article: {e}")
+ continue
except RequestException as e:
print(f"Error fetching from NewsAPI: {e}")
diff --git a/apps/data-processing/src/ingestion/social_fetcher.py b/apps/data-processing/src/ingestion/social_fetcher.py
index 9d68e91d..0ffb81ee 100644
--- a/apps/data-processing/src/ingestion/social_fetcher.py
+++ b/apps/data-processing/src/ingestion/social_fetcher.py
@@ -16,6 +16,7 @@
import requests
from requests.exceptions import RequestException
+from src.utils.translator import translate_and_normalize
logger = logging.getLogger(__name__)
@@ -237,7 +238,7 @@ def fetch_hashtag(
# Normalize hashtag
query = hashtag if hashtag.startswith("#") else f"#{hashtag}"
- query = f"{query} -is:retweet lang:en" # Exclude retweets, English only
+ query = f"{query} -is:retweet (lang:en OR lang:es OR lang:pt)" # Exclude retweets, English/Spanish/Portuguese
params = {
"query": query,
@@ -287,7 +288,7 @@ def fetch_hashtag(
post = SocialPost(
id=tweet["id"],
platform=SocialPlatform.TWITTER.value,
- content=tweet.get("text", ""),
+ content=translate_and_normalize(tweet.get("text", "")),
author=user.get("username", "unknown"),
posted_at=datetime.fromisoformat(tweet["created_at"].replace("Z", "+00:00")),
url=f"https://twitter.com/user/status/{tweet['id']}",
@@ -406,7 +407,7 @@ def fetch_subreddit(
post = SocialPost(
id=post_data.get("id", ""),
platform=SocialPlatform.REDDIT.value,
- content=post_data.get("selftext", "") or post_data.get("title", ""),
+ content=translate_and_normalize(post_data.get("selftext", "") or post_data.get("title", "")),
author=post_data.get("author", "[deleted]"),
posted_at=datetime.fromtimestamp(post_data.get("created_utc", time.time()), tz=timezone.utc),
url=f"https://reddit.com{post_data.get('permalink', '')}",
@@ -475,7 +476,7 @@ def fetch_search(
post = SocialPost(
id=post_data.get("id", ""),
platform=SocialPlatform.REDDIT.value,
- content=post_data.get("selftext", "") or post_data.get("title", ""),
+ content=translate_and_normalize(post_data.get("selftext", "") or post_data.get("title", "")),
author=post_data.get("author", "[deleted]"),
posted_at=datetime.fromtimestamp(post_data.get("created_utc", time.time()), tz=timezone.utc),
url=f"https://reddit.com{post_data.get('permalink', '')}",
diff --git a/apps/data-processing/src/utils/translator.py b/apps/data-processing/src/utils/translator.py
new file mode 100644
index 00000000..c8aa6b5e
--- /dev/null
+++ b/apps/data-processing/src/utils/translator.py
@@ -0,0 +1,88 @@
+import logging
+import unicodedata
+import requests
+from langdetect import detect
+
+logger = logging.getLogger(__name__)
+
+def normalize_text(text: str) -> str:
+ """
+ Applies NFKD unicode normalization, normalizes spacing, and strips text.
+ Keeps casing intact as it is valuable for sentiment analysis.
+ """
+ if not text:
+ return ""
+
+ # NFKD normalization decomposes characters (e.g. accented characters)
+ normalized = unicodedata.normalize("NFKD", text)
+
+ # Clean up whitespace and join
+ lines = normalized.splitlines()
+ cleaned_lines = []
+ for line in lines:
+ cleaned_words = " ".join(line.split())
+ if cleaned_words:
+ cleaned_lines.append(cleaned_words)
+
+ return "\n".join(cleaned_lines).strip()
+
+def translate_to_english(text: str, source_lang: str = "auto") -> str:
+ """
+ Translates non-English text to English using Google's public translation endpoint.
+ If the translation fails or times out, falls back to the original text.
+ """
+ if not text or not text.strip():
+ return text
+
+ url = "https://translate.googleapis.com/translate_a/single"
+ params = {
+ "client": "gtx",
+ "sl": source_lang,
+ "tl": "en",
+ "dt": "t",
+ "q": text
+ }
+
+ try:
+ response = requests.get(url, params=params, timeout=5)
+ response.raise_for_status()
+ data = response.json()
+
+ # Parse the translation chunks returned by Google Translate
+ if data and len(data) > 0 and data[0]:
+ translated_chunks = []
+ for chunk in data[0]:
+ if chunk and len(chunk) > 0 and chunk[0]:
+ translated_chunks.append(chunk[0])
+ if translated_chunks:
+ return "".join(translated_chunks)
+
+ except Exception as e:
+ logger.warning(f"Translation failed, falling back to original text. Error: {e}")
+
+ return text
+
+def translate_and_normalize(text: str) -> str:
+ """
+ Detects the language of the text. If it is not English, normalizes and
+ translates it to English. If it is English, just normalizes it.
+ """
+ if not text or not text.strip():
+ return ""
+
+ # 1. Normalize first (helpful for language detection)
+ normalized = normalize_text(text)
+
+ # 2. Detect language
+ try:
+ lang = detect(normalized)
+ except Exception:
+ # Default to English if detection fails (e.g. no letters)
+ lang = "en"
+
+ # 3. Translate if not English
+ if lang != "en":
+ logger.info(f"Detected language '{lang}'. Translating to English.")
+ return translate_to_english(normalized, source_lang=lang)
+
+ return normalized
diff --git a/apps/webapp/app/community/page.tsx b/apps/webapp/app/community/page.tsx
index 73921109..cc184be1 100644
--- a/apps/webapp/app/community/page.tsx
+++ b/apps/webapp/app/community/page.tsx
@@ -298,7 +298,7 @@ export default function CommunityPage() {
>
CONTRIBUTING.md
- . Here's the short version.
+ . Here's the short version.
{/* vertical line */}
diff --git a/apps/webapp/app/dashboard/page.tsx b/apps/webapp/app/dashboard/page.tsx
index 61ec14a2..a43eca36 100644
--- a/apps/webapp/app/dashboard/page.tsx
+++ b/apps/webapp/app/dashboard/page.tsx
@@ -81,47 +81,163 @@ export default function DashboardPage() {
- {/* Recent Transactions */}
-
-
- Recent Transactions
-
+ {/* Recent Stellar Operations */}
+
+
+
+
+
+
+ Recent Stellar Operations
+
+
+
+ Live Horizon feed
+
+
{!publicKey ? (
-
Connect your Stellar wallet to view transactions.
+
+
+
Connect your Stellar wallet to view recent account activity.
+
) : isLoadingStellar ? (
-
Loading transaction history...
- ) : !transactions || transactions.length === 0 ? (
-
No transactions found for this account.
- ) : (
-
- {transactions.map((tx) => (
-
-
-
- {tx.type.replace(/_/g, " ")}
-
-
- {new Date(tx.created_at).toLocaleString()}
-
-
-
- {tx.amount && (
-
- {parseFloat(tx.amount).toFixed(4)} {tx.asset_code || "XLM"}
-
- )}
-
- View on Explorer
-
+
+ {[1, 2, 3].map((i) => (
+
))}
+ ) : !transactions || transactions.length === 0 ? (
+
+
+
No recent activities found for this account.
+
+ ) : (
+
+ {transactions.map((tx: any) => {
+ let title = "Stellar Operation";
+ let desc = `Operation ID: ${tx.id.substring(0, 8)}...`;
+ let colorStyle = "text-gray-400 bg-gray-500/10 border-gray-500/20";
+ let iconSvg = (
+
+ );
+
+ const isOutgoing = tx.from === publicKey || tx.source_account === publicKey;
+ const isIncoming = tx.to === publicKey || tx.account === publicKey;
+
+ if (tx.type === "payment") {
+ if (isOutgoing) {
+ title = "Payment Sent";
+ desc = `To ${tx.to ? `${tx.to.substring(0, 6)}...${tx.to.substring(50)}` : "unknown"}`;
+ colorStyle = "text-rose-400 bg-rose-500/10 border-rose-500/20";
+ iconSvg = (
+
+ );
+ } else {
+ title = "Payment Received";
+ desc = `From ${tx.from ? `${tx.from.substring(0, 6)}...${tx.from.substring(50)}` : "unknown"}`;
+ colorStyle = "text-emerald-400 bg-emerald-500/10 border-emerald-500/20";
+ iconSvg = (
+
+ );
+ }
+ } else if (tx.type === "create_account") {
+ if (isIncoming) {
+ title = "Account Funded";
+ desc = `Funded with ${tx.starting_balance || "0"} XLM`;
+ colorStyle = "text-emerald-400 bg-emerald-500/10 border-emerald-500/20";
+ } else {
+ title = "Account Created";
+ desc = `Created account ${tx.account ? `${tx.account.substring(0, 6)}...` : ""}`;
+ colorStyle = "text-blue-400 bg-blue-500/10 border-blue-500/20";
+ }
+ iconSvg = (
+
+ );
+ } else if (tx.type === "change_trust") {
+ title = "Trustline Updated";
+ desc = `Asset: ${tx.asset_code || "Unknown"} | Limit: ${tx.limit ? parseFloat(tx.limit).toFixed(0) : "0"}`;
+ colorStyle = "text-cyan-400 bg-cyan-500/10 border-cyan-500/20";
+ iconSvg = (
+
+ );
+ } else if (tx.type === "set_options") {
+ title = "Settings Modified";
+ desc = "Account options / thresholds updated";
+ colorStyle = "text-amber-400 bg-amber-500/10 border-amber-500/20";
+ iconSvg = (
+
+ );
+ }
+
+ return (
+
+
+
+ {iconSvg}
+
+
+
+ {title}
+
+
+ {desc}
+
+
+
+
+ {tx.amount && (
+
+ {parseFloat(tx.amount).toFixed(4)} {tx.asset_code || "XLM"}
+
+ )}
+
+
+ {new Date(tx.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+
+
+ Detail
+
+
+
+
+
+ );
+ })}
+
)}
diff --git a/apps/webapp/app/providers.tsx b/apps/webapp/app/providers.tsx
index 16786e2a..14d77cfb 100644
--- a/apps/webapp/app/providers.tsx
+++ b/apps/webapp/app/providers.tsx
@@ -49,13 +49,20 @@ function ConfigGate({ children }: { children: ReactNode }) {
);
}
+import { OnboardingProvider } from "@/lib/onboarding";
+import { ThemeProvider } from "@/components/theme-provider";
+
export function Providers({ children }: { children: ReactNode }) {
return (
- {children}
+
+
+ {children}
+
+
diff --git a/apps/webapp/components/crypto-table.tsx b/apps/webapp/components/crypto-table.tsx
index 61bd5e67..49c736e4 100644
--- a/apps/webapp/components/crypto-table.tsx
+++ b/apps/webapp/components/crypto-table.tsx
@@ -31,7 +31,9 @@ export function CryptoTable({ formatNumberAction, showWatchlistToggle = true }:
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [favorites, setFavorites] = useState([]);
- const { toggleItem, isInWatchlist } = showWatchlistToggle ? useWatchlist() : { toggleItem: async () => ({ added: false }), isInWatchlist: () => false };
+ const watchlist = useWatchlist();
+ const toggleItem = showWatchlistToggle ? watchlist.toggleItem : async () => ({ added: false });
+ const isInWatchlist = showWatchlistToggle ? watchlist.isInWatchlist : () => false;
// Fetch real crypto data
useEffect(() => {
diff --git a/apps/webapp/components/onboarding/OnboardingModal.tsx b/apps/webapp/components/onboarding/OnboardingModal.tsx
index e6505ae0..58ea44b9 100644
--- a/apps/webapp/components/onboarding/OnboardingModal.tsx
+++ b/apps/webapp/components/onboarding/OnboardingModal.tsx
@@ -1,8 +1,11 @@
"use client";
-import { useEffect, useRef, useCallback } from 'react';
+import { useEffect, useRef, useCallback, useState } from 'react';
import { useOnboarding } from '@/lib/onboarding';
import { useStellarWallet } from '@/app/providers';
+import { useStellarConfig } from '@/contexts/StellarConfigContext';
+import { signTransaction } from '@stellar/freighter-api';
+import { StellarApiService } from '@/lib/api-services';
import Link from 'next/link';
import dynamic from 'next/dynamic';
import gsap from 'gsap';
@@ -36,10 +39,14 @@ const steps = [
export default function OnboardingModal() {
const { state, nextStep, prevStep, closeOnboarding, completeOnboarding, skipOnboarding } = useOnboarding();
- const { status: walletStatus, connect: connectWallet } = useStellarWallet();
+ const { status: walletStatus, connect: connectWallet, publicKey } = useStellarWallet();
+ const { config } = useStellarConfig();
const modalRef = useRef(null);
const stepRef = useRef(null);
+ const [linkingState, setLinkingState] = useState<'idle' | 'fetching_challenge' | 'signing' | 'linking' | 'success' | 'error'>('idle');
+ const [linkingError, setLinkingError] = useState(null);
+
// Auto-advance logic (optional, lightweight)
useEffect(() => {
if (state.step >= 1 && state.step <= 3) {
@@ -70,13 +77,48 @@ export default function OnboardingModal() {
}
}, [state.step]);
+ const handleLinkWallet = useCallback(async () => {
+ if (!publicKey) return;
+ setLinkingState('fetching_challenge');
+ setLinkingError(null);
+ try {
+ const challengeRes = await StellarApiService.getChallenge(publicKey);
+ const challengeXDR = challengeRes.challenge;
+
+ setLinkingState('signing');
+ const networkPassphrase = config?.networkPassphrase;
+ const result = await signTransaction(challengeXDR, { networkPassphrase });
+
+ if (result.error) {
+ throw new Error(result.error);
+ }
+
+ setLinkingState('linking');
+ await StellarApiService.linkAccount(publicKey, result.signedTxXdr, "Onboarding Wallet");
+
+ setLinkingState('success');
+ setTimeout(() => {
+ nextStep();
+ setLinkingState('idle');
+ }, 1500);
+ } catch (err: any) {
+ console.error('Wallet linking error:', err);
+ setLinkingState('error');
+ setLinkingError(err.message || 'Failed to sign or verify challenge.');
+ }
+ }, [publicKey, config, nextStep]);
+
const handleNextOrConnect = useCallback(() => {
- if (state.step === 2 && walletStatus !== 'connected') {
- connectWallet();
+ if (state.step === 2) {
+ if (walletStatus !== 'connected') {
+ connectWallet();
+ } else {
+ handleLinkWallet();
+ }
} else {
nextStep();
}
- }, [state.step, walletStatus, connectWallet, nextStep]);
+ }, [state.step, walletStatus, connectWallet, nextStep, handleLinkWallet]);
const handlePrimaryAction = useCallback(() => {
if (state.step === 3) {
@@ -134,8 +176,15 @@ export default function OnboardingModal() {
/>
+ {/* Link Error Message */}
+ {state.step === 2 && (linkingState === 'error' || linkingError) && (
+
+ {linkingError || "An error occurred during wallet verification."}
+
+ )}
+
{/* Skip Button */}
- {state.step < 3 && (
+ {state.step < 3 && linkingState === 'idle' && (