From 33afb9fe8056e5952255092d2519f6a5fe9dcd45 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Mon, 23 Feb 2026 15:10:38 +0300 Subject: [PATCH 01/10] add vis-network dependency --- infrastructure/control-panel/.npmrc | 2 +- infrastructure/control-panel/package.json | 3 +- pnpm-lock.yaml | 157 ++++++++++++---------- 3 files changed, 89 insertions(+), 73 deletions(-) diff --git a/infrastructure/control-panel/.npmrc b/infrastructure/control-panel/.npmrc index b6f27f135..c0c80ba44 100644 --- a/infrastructure/control-panel/.npmrc +++ b/infrastructure/control-panel/.npmrc @@ -1 +1 @@ -engine-strict=true +engine-strict=false diff --git a/infrastructure/control-panel/package.json b/infrastructure/control-panel/package.json index 2d0d9172d..2ab30fe72 100644 --- a/infrastructure/control-panel/package.json +++ b/infrastructure/control-panel/package.json @@ -52,6 +52,7 @@ "flowbite-svelte-icons": "^2.2.1", "lowdb": "^7.0.1", "lucide-svelte": "^0.561.0", - "tailwind-merge": "^3.0.2" + "tailwind-merge": "^3.0.2", + "vis-network": "^10.0.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8f55c60e..c36a39baf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,6 +157,9 @@ importers: tailwind-merge: specifier: ^3.0.2 version: 3.4.1 + vis-network: + specifier: ^10.0.2 + version: 10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)(keycharm@0.4.0)(uuid@13.0.0)(vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)) devDependencies: '@eslint/compat': specifier: ^1.2.5 @@ -2992,7 +2995,7 @@ importers: version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) draft-js: specifier: ^0.11.7 - version: 0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lucide-react: specifier: ^0.561.0 version: 0.561.0(react@18.3.1) @@ -3013,7 +3016,7 @@ importers: version: 18.3.1(react@18.3.1) react-draft-wysiwyg: specifier: ^1.15.0 - version: 1.15.0(draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.15.0(draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-hook-form: specifier: ^7.55.0 version: 7.71.1(react@18.3.1) @@ -5104,6 +5107,10 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@egjs/hammerjs@2.0.17': + resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} + engines: {node: '>=0.8.0'} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -9613,6 +9620,9 @@ packages: '@types/gtag.js@0.0.12': resolution: {integrity: sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==} + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -11357,6 +11367,10 @@ packages: resolution: {integrity: sha512-j1yoUo4gxPND1JWV9xj5ELih0yMv1iCWDG6eEQIPLSWLxzCXiFoyS7kvB+WwU+tZMf4snwJMMtaubV0laFpiBA==} hasBin: true + component-emitter@2.0.0: + resolution: {integrity: sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==} + engines: {node: '>=18'} + compress-commons@6.0.2: resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} engines: {node: '>= 14'} @@ -14551,6 +14565,9 @@ packages: resolution: {integrity: sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==} hasBin: true + keycharm@0.4.0: + resolution: {integrity: sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -16525,6 +16542,7 @@ packages: prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true prelude-ls@1.2.1: @@ -18932,6 +18950,29 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vis-data@8.0.3: + resolution: {integrity: sha512-jhnb6rJNqkKR1Qmlay0VuDXY9ZlvAnYN1udsrP4U+krgZEq7C0yNSKdZqmnCe13mdnf9AdVcdDGFOzy2mpPoqw==} + peerDependencies: + uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0 + vis-util: '>=6.0.0' + + vis-network@10.0.2: + resolution: {integrity: sha512-qPl8GLYBeHEFqiTqp4VBbYQIJ2EA8KLr7TstA2E8nJxfEHaKCU81hQLz7hhq11NUpHbMaRzBjW5uZpVKJ45/wA==} + peerDependencies: + '@egjs/hammerjs': ^2.0.0 + component-emitter: ^1.3.0 || ^2.0.0 + keycharm: ^0.2.0 || ^0.3.0 || ^0.4.0 + uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0 + vis-data: '>=8.0.0' + vis-util: '>=6.0.0' + + vis-util@6.0.0: + resolution: {integrity: sha512-qtpts3HRma0zPe4bO7t9A2uejkRNj8Z2Tb6do6lN85iPNWExFkUiVhdAq5uLGIUqBFduyYeqWJKv/jMkxX0R5g==} + engines: {node: '>=8'} + peerDependencies: + '@egjs/hammerjs': ^2.0.0 + component-emitter: ^1.3.0 || ^2.0.0 + vite-node@1.6.1: resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -21955,6 +21996,10 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@egjs/hammerjs@2.0.17': + dependencies: + '@types/hammerjs': 2.0.46 + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -27302,6 +27347,8 @@ snapshots: '@types/gtag.js@0.0.12': {} + '@types/hammerjs@2.0.46': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -29576,6 +29623,8 @@ snapshots: minimist: 1.2.8 string.prototype.repeat: 0.2.0 + component-emitter@2.0.0: {} + compress-commons@6.0.2: dependencies: crc-32: 1.2.2 @@ -30516,9 +30565,9 @@ snapshots: dotenv@17.3.1: {} - draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - fbjs: 2.0.0(encoding@0.1.13) + fbjs: 2.0.0 immutable: 3.7.6 object-assign: 4.1.1 react: 18.3.1 @@ -30526,9 +30575,9 @@ snapshots: transitivePeerDependencies: - encoding - draftjs-utils@0.10.2(draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4): + draftjs-utils@0.10.2(draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4): dependencies: - draft-js: 0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + draft-js: 0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) immutable: 5.1.4 drizzle-kit@0.31.9: @@ -30976,8 +31025,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.6.1)) @@ -31040,21 +31089,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3(supports-color@5.5.0) - eslint: 9.39.2(jiti@2.6.1) - get-tsconfig: 4.13.6 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - transitivePeerDependencies: - - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -31097,17 +31131,6 @@ snapshots: - supports-color eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2) - eslint: 9.39.2(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -31147,35 +31170,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.39.2(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 @@ -31187,7 +31181,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.2))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -31860,7 +31854,7 @@ snapshots: fbjs-css-vars@1.0.2: {} - fbjs@2.0.0(encoding@0.1.13): + fbjs@2.0.0: dependencies: core-js: 3.48.0 cross-fetch: 3.2.0(encoding@0.1.13) @@ -32691,9 +32685,9 @@ snapshots: html-tags@3.3.1: {} - html-to-draftjs@1.5.0(draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4): + html-to-draftjs@1.5.0(draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4): dependencies: - draft-js: 0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + draft-js: 0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) immutable: 5.1.4 html-url-attributes@3.0.1: {} @@ -34164,6 +34158,8 @@ snapshots: dependencies: commander: 8.3.0 + keycharm@0.4.0: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -37002,12 +36998,12 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-draft-wysiwyg@1.15.0(draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-draft-wysiwyg@1.15.0(draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: classnames: 2.5.1 - draft-js: 0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - draftjs-utils: 0.10.2(draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4) - html-to-draftjs: 1.5.0(draft-js@0.11.7(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4) + draft-js: 0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + draftjs-utils: 0.10.2(draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4) + html-to-draftjs: 1.5.0(draft-js@0.11.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(immutable@5.1.4) immutable: 5.1.4 linkify-it: 2.2.0 prop-types: 15.8.1 @@ -39567,6 +39563,25 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)): + dependencies: + uuid: 13.0.0 + vis-util: 6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0) + + vis-network@10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)(keycharm@0.4.0)(uuid@13.0.0)(vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)): + dependencies: + '@egjs/hammerjs': 2.0.17 + component-emitter: 2.0.0 + keycharm: 0.4.0 + uuid: 13.0.0 + vis-data: 8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0)) + vis-util: 6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0) + + vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0): + dependencies: + '@egjs/hammerjs': 2.0.17 + component-emitter: 2.0.0 + vite-node@1.6.1(@types/node@20.19.26)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0): dependencies: cac: 6.7.14 From 9b54bbd9e7c892dc5b433b72672cc21d1f3f39c1 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Mon, 23 Feb 2026 19:31:20 +0300 Subject: [PATCH 02/10] feat: add visualizer route and implement reference graph functionality with mock data --- .../control-panel/src/routes/+layout.svelte | 3 +- .../src/routes/visualizer/+page.server.ts | 156 ++++++++ .../src/routes/visualizer/+page.svelte | 354 ++++++++++++++++++ 3 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 infrastructure/control-panel/src/routes/visualizer/+page.server.ts create mode 100644 infrastructure/control-panel/src/routes/visualizer/+page.svelte diff --git a/infrastructure/control-panel/src/routes/+layout.svelte b/infrastructure/control-panel/src/routes/+layout.svelte index bd6d767b8..76f973eea 100644 --- a/infrastructure/control-panel/src/routes/+layout.svelte +++ b/infrastructure/control-panel/src/routes/+layout.svelte @@ -12,7 +12,8 @@ const navLinks = [ { label: 'Dashboard', href: '/' }, { label: 'Monitoring', href: '/monitoring' }, - { label: 'Actions', href: '/actions' } + { label: 'Actions', href: '/actions' }, + { label: 'Visualizer', href: '/visualizer' } ]; const isActive = (href: string) => (href === '/' ? pageUrl === '/' : pageUrl.startsWith(href)); diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.server.ts b/infrastructure/control-panel/src/routes/visualizer/+page.server.ts new file mode 100644 index 000000000..64e9c629c --- /dev/null +++ b/infrastructure/control-panel/src/routes/visualizer/+page.server.ts @@ -0,0 +1,156 @@ +import type { PageServerLoad } from './$types'; + +export interface ReferenceEdge { + id: string; + content: string; + numericScore: number | null; + referenceType: string; + status: string; + targetType: string; // "user" | "group" | "platform" + targetId: string; + targetName: string; + author: { + id: string; + ename: string; + name: string; + }; + createdAt: string; +} + +// ── Mock data generator ──────────────────────────────────────────────── + +const MOCK_USERS = [ + { id: 'u1', ename: 'alice.w3ds', name: 'Alice Dupont' }, + { id: 'u2', ename: 'bob.w3ds', name: 'Bob Martin' }, + { id: 'u3', ename: 'charlie.w3ds', name: 'Charlie Roux' }, + { id: 'u4', ename: 'diana.w3ds', name: 'Diana Chen' }, + { id: 'u5', ename: 'emile.w3ds', name: 'Emile Voss' }, + { id: 'u6', ename: 'fatima.w3ds', name: 'Fatima Nouri' }, + { id: 'u7', ename: 'gabriel.w3ds', name: 'Gabriel Leroy' }, + { id: 'u8', ename: 'hana.w3ds', name: 'Hana Takahashi' }, + { id: 'u9', ename: 'ivan.w3ds', name: 'Ivan Petrov' }, + { id: 'u10', ename: 'julia.w3ds', name: 'Julia Moreau' } +]; + +const MOCK_GROUPS = [ + { id: 'g1', name: 'MetaState Core Team' }, + { id: 'g2', name: 'W3DS Working Group' }, + { id: 'g3', name: 'DAO Governance Board' }, + { id: 'g4', name: 'Open Data Collective' } +]; + +const MOCK_PLATFORMS = [ + { id: 'p1', name: 'Blabsy' }, + { id: 'p2', name: 'Pictique' }, + { id: 'p3', name: 'eVoting' }, + { id: 'p4', name: 'eCurrency' }, + { id: 'p5', name: 'Marketplace' } +]; + +const REFERENCE_TYPES = ['professional', 'academic', 'character', 'skill', 'leadership']; + +const REFERENCE_TEXTS: Record = { + professional: [ + 'Outstanding work ethic and delivery quality. Consistently exceeded expectations.', + 'A reliable professional who brings deep technical expertise to every project.', + 'Excellent collaborator — always willing to help and share knowledge with the team.' + ], + academic: [ + 'Exceptional research capabilities and a talent for synthesizing complex information.', + 'Contributed groundbreaking insights during our joint research project.' + ], + character: [ + 'Trustworthy and principled. A person of integrity in all interactions.', + 'Genuinely kind and supportive, making every team better by being in it.' + ], + skill: [ + 'Expert-level proficiency in distributed systems and cryptographic protocols.', + 'Remarkable problem-solving skills and attention to detail.', + 'Strong leadership in driving technical architecture decisions.' + ], + leadership: [ + 'Led our team through a challenging transition with empathy and clarity.', + 'Inspires others through vision and by example. A natural leader.' + ] +}; + +function pick(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function generateMockReferences(): ReferenceEdge[] { + const references: ReferenceEdge[] = []; + let refIndex = 0; + + // Each user gives 2-4 references to other users, groups, or platforms + for (const author of MOCK_USERS) { + const numRefs = 2 + Math.floor(Math.random() * 3); // 2-4 + + for (let i = 0; i < numRefs; i++) { + const roll = Math.random(); + let targetType: string; + let targetId: string; + let targetName: string; + + if (roll < 0.55) { + // 55% chance: reference another user (not self) + const candidates = MOCK_USERS.filter((u) => u.id !== author.id); + const target = pick(candidates); + targetType = 'user'; + targetId = target.id; + targetName = target.name; + } else if (roll < 0.80) { + // 25% chance: reference a group + const target = pick(MOCK_GROUPS); + targetType = 'group'; + targetId = target.id; + targetName = target.name; + } else { + // 20% chance: reference a platform + const target = pick(MOCK_PLATFORMS); + targetType = 'platform'; + targetId = target.id; + targetName = target.name; + } + + const refType = pick(REFERENCE_TYPES); + const content = pick(REFERENCE_TEXTS[refType]); + const hasScore = Math.random() > 0.3; + const daysAgo = Math.floor(Math.random() * 365); + + references.push({ + id: `ref-${++refIndex}`, + content, + numericScore: hasScore ? Math.floor(Math.random() * 3) + 3 : null, // 3-5 + referenceType: refType, + status: 'signed', + targetType, + targetId, + targetName, + author: { id: author.id, ename: author.ename, name: author.name }, + createdAt: new Date(Date.now() - daysAgo * 86_400_000).toISOString() + }); + } + } + + return references; +} + +// ── Data loader ───────────────────────────────────────────────────────── + +const USE_MOCK = true; + +export const load: PageServerLoad = async ({ fetch }) => { + if (USE_MOCK) { + return { references: generateMockReferences() }; + } + + try { + const response = await fetch('/api/references'); + const data = await response.json(); + return { references: (data.references ?? []) as ReferenceEdge[] }; + } catch (error) { + console.error('Error loading references for visualizer:', error); + return { references: [] as ReferenceEdge[] }; + } +}; diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.svelte b/infrastructure/control-panel/src/routes/visualizer/+page.svelte new file mode 100644 index 000000000..ac501552d --- /dev/null +++ b/infrastructure/control-panel/src/routes/visualizer/+page.svelte @@ -0,0 +1,354 @@ + + +
+
+
+

eReference Network

+

Showing every signed eReference — who vouches for whom across the ecosystem

+
+
+ {data.references?.length ?? 0} references +
+
+ +
+ Author (user) + Target: user + Target: group + Target: platform + → eReference (click edge for details) +
+ +
+
+ + {#if selectedRef} + + {/if} +
+
+ + From 0c5b1fe1362cf624590b51e5885d5f9e8d3f4004 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 09:00:05 +0300 Subject: [PATCH 03/10] connect to db + seed + clean mock ups --- .../src/routes/api/references/+server.ts | 22 ++ .../src/routes/visualizer/+page.server.ts | 126 ---------- .../src/controllers/ReferenceController.ts | 31 +++ .../ereputation/api/src/database/seed.ts | 227 ++++++++++++++++++ platforms/ereputation/api/src/index.ts | 3 + .../api/src/services/ReferenceService.ts | 8 + 6 files changed, 291 insertions(+), 126 deletions(-) create mode 100644 infrastructure/control-panel/src/routes/api/references/+server.ts create mode 100644 platforms/ereputation/api/src/database/seed.ts diff --git a/infrastructure/control-panel/src/routes/api/references/+server.ts b/infrastructure/control-panel/src/routes/api/references/+server.ts new file mode 100644 index 000000000..c8e235781 --- /dev/null +++ b/infrastructure/control-panel/src/routes/api/references/+server.ts @@ -0,0 +1,22 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; + +export const GET: RequestHandler = async () => { + const baseUrl = env.VITE_EREPUTATION_BASE_URL || 'http://localhost:8765'; + + try { + const response = await fetch(`${baseUrl}/api/references/all`); + + if (!response.ok) { + console.error('eReputation API error:', response.status, response.statusText); + return json({ error: 'Failed to fetch references', references: [] }, { status: 500 }); + } + + const data = await response.json(); + return json(data); + } catch (error) { + console.error('Error fetching references from eReputation:', error); + return json({ error: 'Failed to connect to eReputation API', references: [] }, { status: 500 }); + } +}; diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.server.ts b/infrastructure/control-panel/src/routes/visualizer/+page.server.ts index 64e9c629c..f0335f361 100644 --- a/infrastructure/control-panel/src/routes/visualizer/+page.server.ts +++ b/infrastructure/control-panel/src/routes/visualizer/+page.server.ts @@ -17,134 +17,8 @@ export interface ReferenceEdge { createdAt: string; } -// ── Mock data generator ──────────────────────────────────────────────── - -const MOCK_USERS = [ - { id: 'u1', ename: 'alice.w3ds', name: 'Alice Dupont' }, - { id: 'u2', ename: 'bob.w3ds', name: 'Bob Martin' }, - { id: 'u3', ename: 'charlie.w3ds', name: 'Charlie Roux' }, - { id: 'u4', ename: 'diana.w3ds', name: 'Diana Chen' }, - { id: 'u5', ename: 'emile.w3ds', name: 'Emile Voss' }, - { id: 'u6', ename: 'fatima.w3ds', name: 'Fatima Nouri' }, - { id: 'u7', ename: 'gabriel.w3ds', name: 'Gabriel Leroy' }, - { id: 'u8', ename: 'hana.w3ds', name: 'Hana Takahashi' }, - { id: 'u9', ename: 'ivan.w3ds', name: 'Ivan Petrov' }, - { id: 'u10', ename: 'julia.w3ds', name: 'Julia Moreau' } -]; - -const MOCK_GROUPS = [ - { id: 'g1', name: 'MetaState Core Team' }, - { id: 'g2', name: 'W3DS Working Group' }, - { id: 'g3', name: 'DAO Governance Board' }, - { id: 'g4', name: 'Open Data Collective' } -]; - -const MOCK_PLATFORMS = [ - { id: 'p1', name: 'Blabsy' }, - { id: 'p2', name: 'Pictique' }, - { id: 'p3', name: 'eVoting' }, - { id: 'p4', name: 'eCurrency' }, - { id: 'p5', name: 'Marketplace' } -]; - -const REFERENCE_TYPES = ['professional', 'academic', 'character', 'skill', 'leadership']; - -const REFERENCE_TEXTS: Record = { - professional: [ - 'Outstanding work ethic and delivery quality. Consistently exceeded expectations.', - 'A reliable professional who brings deep technical expertise to every project.', - 'Excellent collaborator — always willing to help and share knowledge with the team.' - ], - academic: [ - 'Exceptional research capabilities and a talent for synthesizing complex information.', - 'Contributed groundbreaking insights during our joint research project.' - ], - character: [ - 'Trustworthy and principled. A person of integrity in all interactions.', - 'Genuinely kind and supportive, making every team better by being in it.' - ], - skill: [ - 'Expert-level proficiency in distributed systems and cryptographic protocols.', - 'Remarkable problem-solving skills and attention to detail.', - 'Strong leadership in driving technical architecture decisions.' - ], - leadership: [ - 'Led our team through a challenging transition with empathy and clarity.', - 'Inspires others through vision and by example. A natural leader.' - ] -}; - -function pick(arr: T[]): T { - return arr[Math.floor(Math.random() * arr.length)]; -} - -function generateMockReferences(): ReferenceEdge[] { - const references: ReferenceEdge[] = []; - let refIndex = 0; - - // Each user gives 2-4 references to other users, groups, or platforms - for (const author of MOCK_USERS) { - const numRefs = 2 + Math.floor(Math.random() * 3); // 2-4 - - for (let i = 0; i < numRefs; i++) { - const roll = Math.random(); - let targetType: string; - let targetId: string; - let targetName: string; - - if (roll < 0.55) { - // 55% chance: reference another user (not self) - const candidates = MOCK_USERS.filter((u) => u.id !== author.id); - const target = pick(candidates); - targetType = 'user'; - targetId = target.id; - targetName = target.name; - } else if (roll < 0.80) { - // 25% chance: reference a group - const target = pick(MOCK_GROUPS); - targetType = 'group'; - targetId = target.id; - targetName = target.name; - } else { - // 20% chance: reference a platform - const target = pick(MOCK_PLATFORMS); - targetType = 'platform'; - targetId = target.id; - targetName = target.name; - } - - const refType = pick(REFERENCE_TYPES); - const content = pick(REFERENCE_TEXTS[refType]); - const hasScore = Math.random() > 0.3; - const daysAgo = Math.floor(Math.random() * 365); - - references.push({ - id: `ref-${++refIndex}`, - content, - numericScore: hasScore ? Math.floor(Math.random() * 3) + 3 : null, // 3-5 - referenceType: refType, - status: 'signed', - targetType, - targetId, - targetName, - author: { id: author.id, ename: author.ename, name: author.name }, - createdAt: new Date(Date.now() - daysAgo * 86_400_000).toISOString() - }); - } - } - - return references; -} - -// ── Data loader ───────────────────────────────────────────────────────── - -const USE_MOCK = true; export const load: PageServerLoad = async ({ fetch }) => { - if (USE_MOCK) { - return { references: generateMockReferences() }; - } - try { const response = await fetch('/api/references'); const data = await response.json(); diff --git a/platforms/ereputation/api/src/controllers/ReferenceController.ts b/platforms/ereputation/api/src/controllers/ReferenceController.ts index e250c6854..a015c4e84 100644 --- a/platforms/ereputation/api/src/controllers/ReferenceController.ts +++ b/platforms/ereputation/api/src/controllers/ReferenceController.ts @@ -73,6 +73,37 @@ export class ReferenceController { } }; + /** + * Get ALL signed references in the system (admin/visualizer endpoint). + */ + getAllReferences = async (_req: Request, res: Response) => { + try { + const references = await this.referenceService.getAllReferences(); + + res.json({ + references: references.map(ref => ({ + id: ref.id, + content: ref.content, + numericScore: ref.numericScore, + referenceType: ref.referenceType, + status: ref.status, + targetType: ref.targetType, + targetId: ref.targetId, + targetName: ref.targetName, + author: { + id: ref.author.id, + ename: ref.author.ename, + name: ref.author.name + }, + createdAt: ref.createdAt + })) + }); + } catch (error) { + console.error("Error getting all references:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; + getReferencesForTarget = async (req: Request, res: Response) => { try { const { targetType, targetId } = req.params; diff --git a/platforms/ereputation/api/src/database/seed.ts b/platforms/ereputation/api/src/database/seed.ts new file mode 100644 index 000000000..78868f5f6 --- /dev/null +++ b/platforms/ereputation/api/src/database/seed.ts @@ -0,0 +1,227 @@ +/** + * Seed script for eReputation database. + * Creates mock users and signed eReferences for the visualizer. + * + * Usage (from platforms/ereputation/api): + * npx ts-node src/database/seed.ts + */ +import "reflect-metadata"; +import path from "node:path"; +import { config } from "dotenv"; +import { DataSource } from "typeorm"; +import { User } from "./entities/User"; +import { Reference } from "./entities/Reference"; +import { Vote } from "./entities/Vote"; +import { Poll } from "./entities/Poll"; +import { Wishlist } from "./entities/Wishlist"; +import { VoteReputationResult } from "./entities/VoteReputationResult"; +import { ReferenceSignature } from "./entities/ReferenceSignature"; +import { Group } from "./entities/Group"; +import { Calculation } from "./entities/Calculation"; +import { Message } from "./entities/Message"; + +config({ path: path.resolve(__dirname, "../../../../.env") }); + +// Parse DB URL into explicit connection options (avoids SCRAM password parsing issues) +function parseDbUrl(url: string) { + const parsed = new URL(url); + return { + host: parsed.hostname, + port: parseInt(parsed.port || "5432", 10), + username: decodeURIComponent(parsed.username), + password: decodeURIComponent(parsed.password), + database: parsed.pathname.replace(/^\//, ""), + }; +} + +const dbConfig = parseDbUrl(process.env.EREPUTATION_DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ereputation"); + +// Standalone data source (no subscribers, no migrations) +const SeedDataSource = new DataSource({ + type: "postgres", + ...dbConfig, + synchronize: true, // create tables if missing + entities: [User, Reference, Vote, Poll, Wishlist, VoteReputationResult, ReferenceSignature, Group, Calculation, Message], + logging: false, +}); + +// ── Seed data ────────────────────────────────────────────────────────── + +const USERS = [ + { handle: "alice", name: "Alice Dupont", ename: "alice.w3ds", email: "alice@example.com" }, + { handle: "bob", name: "Bob Martin", ename: "bob.w3ds", email: "bob@example.com" }, + { handle: "charlie", name: "Charlie Roux", ename: "charlie.w3ds", email: "charlie@example.com" }, + { handle: "diana", name: "Diana Chen", ename: "diana.w3ds", email: "diana@example.com" }, + { handle: "emile", name: "Emile Voss", ename: "emile.w3ds", email: "emile@example.com" }, + { handle: "fatima", name: "Fatima Nouri", ename: "fatima.w3ds", email: "fatima@example.com" }, + { handle: "gabriel", name: "Gabriel Leroy", ename: "gabriel.w3ds", email: "gabriel@example.com" }, + { handle: "hana", name: "Hana Takahashi", ename: "hana.w3ds", email: "hana@example.com" }, + { handle: "ivan", name: "Ivan Petrov", ename: "ivan.w3ds", email: "ivan@example.com" }, + { handle: "julia", name: "Julia Moreau", ename: "julia.w3ds", email: "julia@example.com" }, + { handle: "karl", name: "Karl Weber", ename: "karl.w3ds", email: "karl@example.com" }, + { handle: "lina", name: "Lina Alves", ename: "lina.w3ds", email: "lina@example.com" }, + { handle: "marco", name: "Marco Rossi", ename: "marco.w3ds", email: "marco@example.com" }, + { handle: "nadia", name: "Nadia Sergeeva", ename: "nadia.w3ds", email: "nadia@example.com" }, + { handle: "omar", name: "Omar Farah", ename: "omar.w3ds", email: "omar@example.com" }, +]; + +const GROUPS = [ + { id: "g-core-team", name: "MetaState Core Team" }, + { id: "g-w3ds-wg", name: "W3DS Working Group" }, + { id: "g-dao-gov", name: "DAO Governance Board" }, + { id: "g-open-data", name: "Open Data Collective" }, +]; + +const PLATFORMS = [ + { id: "p-blabsy", name: "Blabsy" }, + { id: "p-pictique", name: "Pictique" }, + { id: "p-evoting", name: "eVoting" }, + { id: "p-ecurrency", name: "eCurrency" }, + { id: "p-marketplace", name: "Marketplace" }, + { id: "p-esigner", name: "eSigner" }, +]; + +const REF_TYPES = ["professional", "academic", "character", "skill", "leadership"]; + +const TEXTS: Record = { + professional: [ + "Outstanding work ethic and delivery quality. Consistently exceeded expectations on every project we collaborated on.", + "A reliable professional who brings deep technical expertise to every project. I would hire them again without hesitation.", + "Excellent collaborator — always willing to help and share knowledge with the team. Elevated everyone around them.", + "Demonstrated exceptional project management skills and always delivered on time despite tight deadlines.", + ], + academic: [ + "Exceptional research capabilities and a talent for synthesizing complex information into actionable insights.", + "Contributed groundbreaking insights during our joint research project on decentralized identity systems.", + "A brilliant mind with a knack for breaking down complex cryptographic concepts for broader audiences.", + ], + character: [ + "Trustworthy and principled. A person of integrity in all interactions, both professional and personal.", + "Genuinely kind and supportive, making every team better by being in it. Their positive energy is contagious.", + "An exceptionally honest and transparent individual. You always know where you stand with them.", + ], + skill: [ + "Expert-level proficiency in distributed systems and cryptographic protocols. A true technical authority.", + "Remarkable problem-solving skills and attention to detail. Finds solutions others wouldn't think of.", + "Strong architectural thinking — designs systems that are both elegant and resilient at scale.", + "Full-stack expertise with deep knowledge of both frontend UX and backend infrastructure.", + ], + leadership: [ + "Led our team through a challenging transition with empathy and clarity. Everyone felt supported.", + "Inspires others through vision and by example. A natural leader who earns respect rather than demands it.", + "Exceptional at mentoring junior team members. Several people on our team grew significantly under their guidance.", + ], +}; + +function pick(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function randomDate(daysBack: number): Date { + return new Date(Date.now() - Math.floor(Math.random() * daysBack) * 86_400_000); +} + +// ── Main ─────────────────────────────────────────────────────────────── + +async function seed() { + console.log("Connecting to eReputation database..."); + await SeedDataSource.initialize(); + console.log("Connected.\n"); + + const userRepo = SeedDataSource.getRepository(User); + const refRepo = SeedDataSource.getRepository(Reference); + + // 1. Upsert users + console.log(`Seeding ${USERS.length} users...`); + const savedUsers: User[] = []; + for (const u of USERS) { + let existing = await userRepo.findOneBy({ ename: u.ename }); + if (!existing) { + existing = userRepo.create(u); + existing = await userRepo.save(existing); + console.log(` + Created user: ${u.name} (${u.ename})`); + } else { + console.log(` · Exists: ${u.name} (${u.ename})`); + } + savedUsers.push(existing); + } + + // 2. Clear old seed references (optional, idempotent re-runs) + const existingRefCount = await refRepo.count(); + if (existingRefCount > 0) { + console.log(`\nClearing ${existingRefCount} existing references...`); + await refRepo.clear(); + } + + // 3. Generate references + console.log("\nGenerating eReferences..."); + const references: Partial[] = []; + + for (const author of savedUsers) { + const numRefs = 2 + Math.floor(Math.random() * 4); // 2-5 references each + + for (let i = 0; i < numRefs; i++) { + const roll = Math.random(); + let targetType: string; + let targetId: string; + let targetName: string; + + if (roll < 0.55) { + // User target (not self) + const candidates = savedUsers.filter((u) => u.id !== author.id); + const target = pick(candidates); + targetType = "user"; + targetId = target.id; + targetName = target.name; + } else if (roll < 0.80) { + // Group target + const target = pick(GROUPS); + targetType = "group"; + targetId = target.id; + targetName = target.name; + } else { + // Platform target + const target = pick(PLATFORMS); + targetType = "platform"; + targetId = target.id; + targetName = target.name; + } + + const refType = pick(REF_TYPES); + const content = pick(TEXTS[refType]); + const hasScore = Math.random() > 0.25; + + references.push({ + authorId: author.id, + targetType, + targetId, + targetName, + content, + referenceType: refType, + numericScore: hasScore ? Math.floor(Math.random() * 3) + 3 : undefined, // 3-5 + status: "signed", + createdAt: randomDate(365), + }); + } + } + + // Bulk insert + await refRepo.save(references as Reference[]); + console.log(` ✓ Created ${references.length} signed eReferences\n`); + + // 4. Summary + const userCount = await userRepo.count(); + const refCount = await refRepo.count(); + console.log("── Seed complete ──"); + console.log(` Users: ${userCount}`); + console.log(` References: ${refCount}`); + console.log(""); + + await SeedDataSource.destroy(); + process.exit(0); +} + +seed().catch((err) => { + console.error("Seed failed:", err); + process.exit(1); +}); diff --git a/platforms/ereputation/api/src/index.ts b/platforms/ereputation/api/src/index.ts index 2ad365765..7274d95c0 100644 --- a/platforms/ereputation/api/src/index.ts +++ b/platforms/ereputation/api/src/index.ts @@ -85,6 +85,9 @@ app.post("/api/webhook", webhookController.handleWebhook); app.get("/api/platforms", platformController.getPlatforms); app.get("/api/platforms/search", platformController.searchPlatforms); +// Public reference routes (for visualizer / admin) +app.get("/api/references/all", referenceController.getAllReferences); + // Protected routes (auth required) app.use(authMiddleware); // Apply auth middleware to all routes below diff --git a/platforms/ereputation/api/src/services/ReferenceService.ts b/platforms/ereputation/api/src/services/ReferenceService.ts index 17f5a314d..189fbb6b9 100644 --- a/platforms/ereputation/api/src/services/ReferenceService.ts +++ b/platforms/ereputation/api/src/services/ReferenceService.ts @@ -82,6 +82,14 @@ export class ReferenceService { return { references, total }; } + async getAllReferences(): Promise { + return await this.referenceRepository.find({ + where: { status: "signed" }, + relations: ["author"], + order: { createdAt: "DESC" } + }); + } + async revokeReference(referenceId: string, authorId: string): Promise { const reference = await this.referenceRepository.findOne({ where: { id: referenceId, authorId } From b637ba6a030aa517c4bebeeb74160fcf9cc88eb7 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 09:06:23 +0300 Subject: [PATCH 04/10] fix graph physics --- .../src/routes/visualizer/+page.svelte | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.svelte b/infrastructure/control-panel/src/routes/visualizer/+page.svelte index ac501552d..1a15a69d8 100644 --- a/infrastructure/control-panel/src/routes/visualizer/+page.svelte +++ b/infrastructure/control-panel/src/routes/visualizer/+page.svelte @@ -114,16 +114,17 @@ }, physics: { barnesHut: { - gravitationalConstant: -8000, // strong repulsion pushes nodes apart - centralGravity: 0.3, // pulls everything toward center - springLength: 250, // longer springs = more spacing - springConstant: 0.04, // softer springs = less jitter - damping: 0.09, - avoidOverlap: 0.5 // prevents node overlap + gravitationalConstant: -8000, + centralGravity: 0.3, + springLength: 250, + springConstant: 0.04, + damping: 0.5, // high damping = stops oscillating quickly + avoidOverlap: 0.5 }, maxVelocity: 50, + minVelocity: 0.75, // stop physics once nodes are nearly still solver: 'barnesHut', - stabilization: { iterations: 300, fit: true } + stabilization: { enabled: true, iterations: 300, fit: true } }, interaction: { hover: true, @@ -135,6 +136,11 @@ const network = new Network(container, networkData, options); + // Disable physics once the initial layout is done — prevents endless spinning + network.on('stabilizationIterationsDone', () => { + network.setOptions({ physics: false }); + }); + network.on('click', (params) => { if (params.edges.length > 0) { const edgeId = params.edges[0]; From e63c7905b2ef07c66473f50e084b95e3850210de Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 10:26:53 +0300 Subject: [PATCH 05/10] feat: enhance visualizer with node detail display and interaction --- .../src/routes/visualizer/+page.svelte | 436 ++++++++++-------- 1 file changed, 250 insertions(+), 186 deletions(-) diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.svelte b/infrastructure/control-panel/src/routes/visualizer/+page.svelte index 1a15a69d8..57aeca5ff 100644 --- a/infrastructure/control-panel/src/routes/visualizer/+page.svelte +++ b/infrastructure/control-panel/src/routes/visualizer/+page.svelte @@ -2,13 +2,21 @@ import type { PageProps } from './$types'; import type { ReferenceEdge } from './+page.server'; import { onMount } from 'svelte'; + import { slide } from 'svelte/transition'; import { Network } from 'vis-network'; import type { Data, Options } from 'vis-network'; let { data }: PageProps = $props(); let container: HTMLDivElement; - let selectedRef: ReferenceEdge | null = $state(null); + type NodeDetail = { + id: string; + label: string; + outgoing: { ref: ReferenceEdge; targetLabel: string }[]; + incoming: { ref: ReferenceEdge; authorLabel: string }[]; + }; + + let selectedNode: NodeDetail | null = $state(null); // Color palette per target type const TARGET_COLORS: Record = { @@ -81,7 +89,7 @@ }; }); - return { nodes, edges }; + return { nodes, edges, nodeMap }; } onMount(() => { @@ -118,7 +126,7 @@ centralGravity: 0.3, springLength: 250, springConstant: 0.04, - damping: 0.5, // high damping = stops oscillating quickly + damping: 0.5, // high damping = stops oscillating quickly avoidOverlap: 0.5 }, maxVelocity: 50, @@ -142,219 +150,275 @@ }); network.on('click', (params) => { - if (params.edges.length > 0) { - const edgeId = params.edges[0]; - const edge = graph.edges[edgeId]; - if (edge) { - selectedRef = edge.refData; - } + if (params.nodes.length > 0) { + const nodeId = params.nodes[0] as string; + const nodeInfo = graph.nodeMap.get(nodeId); + const label = nodeInfo?.label ?? nodeId; + + const outgoing = graph.edges + .filter((e) => e.from === nodeId) + .map((e) => ({ + ref: e.refData, + targetLabel: graph.nodeMap.get(e.to)?.label ?? e.to + })); + + const incoming = graph.edges + .filter((e) => e.to === nodeId) + .map((e) => ({ + ref: e.refData, + authorLabel: graph.nodeMap.get(e.from)?.label ?? e.from + })); + + selectedNode = { id: nodeId, label, outgoing, incoming }; } else { - selectedRef = null; + selectedNode = null; } }); }); -
-
+
+ +
-

eReference Network

-

Showing every signed eReference — who vouches for whom across the ecosystem

+

eReference Network

+

+ Showing every signed eReference — who vouches for whom across the ecosystem +

-
- {data.references?.length ?? 0} references +
+ + {data.references?.length ?? 0} references +
-
- Author (user) - Target: user - Target: group - Target: platform +
+ + + Author (user) + + + + Target: user + + + + Target: group + + + + Target: platform + + → eReference (click node for details) - → eReference (click edge for details)
-
-
- - {#if selectedRef} -
From 435019661aef6f7fd0150c6e72555a47da32705c Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 11:31:58 +0300 Subject: [PATCH 06/10] feat: add key block for selected node detail panel to optimize rendering --- infrastructure/control-panel/src/routes/visualizer/+page.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.svelte b/infrastructure/control-panel/src/routes/visualizer/+page.svelte index 57aeca5ff..ff7f68b80 100644 --- a/infrastructure/control-panel/src/routes/visualizer/+page.svelte +++ b/infrastructure/control-panel/src/routes/visualizer/+page.svelte @@ -229,6 +229,7 @@ out:slide={{ axis: 'x', duration: 150 }} > {#if selectedNode} + {#key selectedNode.id}

{selectedNode.label}

@@ -367,6 +368,7 @@ No references for this node.

{/if} + {/key} {:else}

Click a node to see details about its references. From 55fc876861125d21e1b693971c268645d3f07d4e Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 12:04:30 +0300 Subject: [PATCH 07/10] Minor fixes --- infrastructure/control-panel/.npmrc | 1 + .../src/routes/api/references/+server.ts | 6 +- .../src/routes/visualizer/+page.svelte | 252 +++++++++--------- .../src/controllers/ReferenceController.ts | 4 +- .../ereputation/api/src/database/seed.ts | 13 +- 5 files changed, 145 insertions(+), 131 deletions(-) diff --git a/infrastructure/control-panel/.npmrc b/infrastructure/control-panel/.npmrc index c0c80ba44..f8ccccb9b 100644 --- a/infrastructure/control-panel/.npmrc +++ b/infrastructure/control-panel/.npmrc @@ -1 +1,2 @@ +# vis-network required node < 22 engine-strict=false diff --git a/infrastructure/control-panel/src/routes/api/references/+server.ts b/infrastructure/control-panel/src/routes/api/references/+server.ts index c8e235781..7942de98c 100644 --- a/infrastructure/control-panel/src/routes/api/references/+server.ts +++ b/infrastructure/control-panel/src/routes/api/references/+server.ts @@ -3,10 +3,14 @@ import type { RequestHandler } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; export const GET: RequestHandler = async () => { - const baseUrl = env.VITE_EREPUTATION_BASE_URL || 'http://localhost:8765'; + const baseUrl = env.EREPUTATION_BASE_URL || 'http://localhost:8765'; try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); // 5 seconds timeout const response = await fetch(`${baseUrl}/api/references/all`); + clearTimeout(timeout); + if (!response.ok) { console.error('eReputation API error:', response.status, response.statusText); diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.svelte b/infrastructure/control-panel/src/routes/visualizer/+page.svelte index ff7f68b80..104ac1a25 100644 --- a/infrastructure/control-panel/src/routes/visualizer/+page.svelte +++ b/infrastructure/control-panel/src/routes/visualizer/+page.svelte @@ -61,7 +61,7 @@ // Edge const typeLabel = ref.referenceType || 'general'; - const scoreLabel = ref.numericScore ? ` (${ref.numericScore}/5)` : ''; + const scoreLabel = ref.numericScore != null ? ` (${ref.numericScore}/5)` : ''; edges.push({ from: authorKey, to: targetKey, @@ -174,6 +174,9 @@ selectedNode = null; } }); + return () => { + network.destroy(); + }; }); @@ -230,144 +233,149 @@ > {#if selectedNode} {#key selectedNode.id} - -

-

{selectedNode.label}

- -
+ +
+

+ {selectedNode.label} +

+ +
- - {#if selectedNode.outgoing.length > 0} -
- - Outgoing - - {selectedNode.outgoing.length} - - -
- {#each selectedNode.outgoing as item} -
+ {#if selectedNode.outgoing.length > 0} +
+ + Outgoing + - - - - {item.targetLabel} - - - {item.ref.referenceType} - - -
- {#if item.ref.numericScore} + {selectedNode.outgoing.length} + +
+
+ {#each selectedNode.outgoing as item} +
+ + + + {item.targetLabel} + + + {item.ref.referenceType} + + +
+ {#if item.ref.numericScore} +
+ Score + {item.ref.numericScore} / 5 +
+ {/if}
- ScoreDate + {new Date( + item.ref.createdAt + ).toLocaleDateString()} - {item.ref.numericScore} / 5
- {/if} -
- Date - {new Date( - item.ref.createdAt - ).toLocaleDateString()} +

+ {item.ref.content} +

-

- {item.ref.content} -

-
-
- {/each} -
-
- {/if} +
+ {/each} +
+
+ {/if} - - {#if selectedNode.incoming.length > 0} -
- - Incoming - - {selectedNode.incoming.length} - - -
- {#each selectedNode.incoming as item} -
+ {#if selectedNode.incoming.length > 0} +
+ + Incoming + - - - - {item.authorLabel} - - - {item.ref.referenceType} - - -
- {#if item.ref.numericScore} + {selectedNode.incoming.length} + +
+
+ {#each selectedNode.incoming as item} +
+ + + + {item.authorLabel} + + + {item.ref.referenceType} + + +
+ {#if item.ref.numericScore} +
+ Score + {item.ref.numericScore} / 5 +
+ {/if}
- ScoreDate + {new Date( + item.ref.createdAt + ).toLocaleDateString()} - {item.ref.numericScore} / 5
- {/if} -
- Date - {new Date( - item.ref.createdAt - ).toLocaleDateString()} +

+ {item.ref.content} +

-

- {item.ref.content} -

-
-
- {/each} -
-
- {/if} +
+ {/each} +
+
+ {/if} - {#if selectedNode.outgoing.length === 0 && selectedNode.incoming.length === 0} -

- No references for this node. -

- {/if} + {#if selectedNode.outgoing.length === 0 && selectedNode.incoming.length === 0} +

+ No references for this node. +

+ {/if} {/key} {:else}

diff --git a/platforms/ereputation/api/src/controllers/ReferenceController.ts b/platforms/ereputation/api/src/controllers/ReferenceController.ts index a015c4e84..3fc471398 100644 --- a/platforms/ereputation/api/src/controllers/ReferenceController.ts +++ b/platforms/ereputation/api/src/controllers/ReferenceController.ts @@ -90,11 +90,11 @@ export class ReferenceController { targetType: ref.targetType, targetId: ref.targetId, targetName: ref.targetName, - author: { + author: ref.author ?{ id: ref.author.id, ename: ref.author.ename, name: ref.author.name - }, + } : null, createdAt: ref.createdAt })) }); diff --git a/platforms/ereputation/api/src/database/seed.ts b/platforms/ereputation/api/src/database/seed.ts index 78868f5f6..92f81c322 100644 --- a/platforms/ereputation/api/src/database/seed.ts +++ b/platforms/ereputation/api/src/database/seed.ts @@ -8,7 +8,7 @@ import "reflect-metadata"; import path from "node:path"; import { config } from "dotenv"; -import { DataSource } from "typeorm"; +import { DataSource, In } from "typeorm"; import { User } from "./entities/User"; import { Reference } from "./entities/Reference"; import { Vote } from "./entities/Vote"; @@ -40,7 +40,7 @@ const dbConfig = parseDbUrl(process.env.EREPUTATION_DATABASE_URL || "postgresql: const SeedDataSource = new DataSource({ type: "postgres", ...dbConfig, - synchronize: true, // create tables if missing + synchronize: false, entities: [User, Reference, Vote, Poll, Wishlist, VoteReputationResult, ReferenceSignature, Group, Calculation, Message], logging: false, }); @@ -146,11 +146,12 @@ async function seed() { savedUsers.push(existing); } - // 2. Clear old seed references (optional, idempotent re-runs) - const existingRefCount = await refRepo.count(); + // 2. Clear old seed references (scoped to seed users only — preserves real user data) + const seedUserIds = savedUsers.map((u) => u.id); + const existingRefCount = await refRepo.count({ where: { authorId: In(seedUserIds) } }); if (existingRefCount > 0) { - console.log(`\nClearing ${existingRefCount} existing references...`); - await refRepo.clear(); + console.log(`\nClearing ${existingRefCount} existing seed references...`); + await refRepo.delete({ authorId: In(seedUserIds) }); } // 3. Generate references From c585ff7c96bbd391d72f2ec42c173a47a92ed537 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 21:24:16 +0300 Subject: [PATCH 08/10] feat: enhance reference fetching with API key validation and add missing headers --- .../control-panel/src/routes/api/references/+server.ts | 6 ++++-- platforms/blabsy/next-env.d.ts | 6 ++++++ .../api/src/controllers/ReferenceController.ts | 10 ++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 platforms/blabsy/next-env.d.ts diff --git a/infrastructure/control-panel/src/routes/api/references/+server.ts b/infrastructure/control-panel/src/routes/api/references/+server.ts index 7942de98c..063904ba2 100644 --- a/infrastructure/control-panel/src/routes/api/references/+server.ts +++ b/infrastructure/control-panel/src/routes/api/references/+server.ts @@ -7,8 +7,10 @@ export const GET: RequestHandler = async () => { try { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5000); // 5 seconds timeout - const response = await fetch(`${baseUrl}/api/references/all`); + const timeout = setTimeout(() => controller.abort(), 5000); + const response = await fetch(`${baseUrl}/api/references/all`, { + headers: env.VISUALIZER_API_KEY ? { 'x-visualizer-key': env.VISUALIZER_API_KEY } : {} + }); clearTimeout(timeout); diff --git a/platforms/blabsy/next-env.d.ts b/platforms/blabsy/next-env.d.ts new file mode 100644 index 000000000..254b73c16 --- /dev/null +++ b/platforms/blabsy/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/platforms/ereputation/api/src/controllers/ReferenceController.ts b/platforms/ereputation/api/src/controllers/ReferenceController.ts index 3fc471398..0588d20ab 100644 --- a/platforms/ereputation/api/src/controllers/ReferenceController.ts +++ b/platforms/ereputation/api/src/controllers/ReferenceController.ts @@ -74,9 +74,15 @@ export class ReferenceController { }; /** - * Get ALL signed references in the system (admin/visualizer endpoint). + * Get ALL signed references in the system (internal visualizer endpoint). + * Requires X-Visualizer-Key header matching VISUALIZER_API_KEY env var. */ - getAllReferences = async (_req: Request, res: Response) => { + getAllReferences = async (req: Request, res: Response) => { + const apiKey = process.env.VISUALIZER_API_KEY; + if (apiKey && req.headers['x-visualizer-key'] !== apiKey) { + return res.status(401).json({ error: 'Unauthorized' }); + } + try { const references = await this.referenceService.getAllReferences(); From 9c1fb208c2b76b0f3820d7efcf4e199095965435 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 21:27:22 +0300 Subject: [PATCH 09/10] feat: add pagination support to getAllReferences method --- platforms/ereputation/api/src/services/ReferenceService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/platforms/ereputation/api/src/services/ReferenceService.ts b/platforms/ereputation/api/src/services/ReferenceService.ts index 189fbb6b9..a97e1dfb5 100644 --- a/platforms/ereputation/api/src/services/ReferenceService.ts +++ b/platforms/ereputation/api/src/services/ReferenceService.ts @@ -82,11 +82,13 @@ export class ReferenceService { return { references, total }; } - async getAllReferences(): Promise { + async getAllReferences(limit = 500, offset = 0): Promise { return await this.referenceRepository.find({ where: { status: "signed" }, relations: ["author"], - order: { createdAt: "DESC" } + order: { createdAt: "DESC" }, + take: limit, + skip: offset, }); } From 186b6b2f12e8e2184b3687fb6a93de872b8a74b5 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 24 Feb 2026 21:56:25 +0300 Subject: [PATCH 10/10] feat: enhance getAllReferences method with pagination and API key validation --- .../src/routes/api/references/+server.ts | 3 ++- .../src/routes/visualizer/+page.svelte | 1 + .../api/src/controllers/ReferenceController.ts | 13 +++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/infrastructure/control-panel/src/routes/api/references/+server.ts b/infrastructure/control-panel/src/routes/api/references/+server.ts index 063904ba2..6cbf437f0 100644 --- a/infrastructure/control-panel/src/routes/api/references/+server.ts +++ b/infrastructure/control-panel/src/routes/api/references/+server.ts @@ -9,7 +9,8 @@ export const GET: RequestHandler = async () => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const response = await fetch(`${baseUrl}/api/references/all`, { - headers: env.VISUALIZER_API_KEY ? { 'x-visualizer-key': env.VISUALIZER_API_KEY } : {} + headers: env.VISUALIZER_API_KEY ? { 'x-visualizer-key': env.VISUALIZER_API_KEY } : {}, + signal: controller.signal, }); clearTimeout(timeout); diff --git a/infrastructure/control-panel/src/routes/visualizer/+page.svelte b/infrastructure/control-panel/src/routes/visualizer/+page.svelte index 104ac1a25..2f845e4d5 100644 --- a/infrastructure/control-panel/src/routes/visualizer/+page.svelte +++ b/infrastructure/control-panel/src/routes/visualizer/+page.svelte @@ -35,6 +35,7 @@ const degree = new Map(); for (const ref of references) { + if (!ref.author) continue; // skip orphaned references // Author node (always a user) const authorKey = `user:${ref.author.id}`; if (!nodeMap.has(authorKey)) { diff --git a/platforms/ereputation/api/src/controllers/ReferenceController.ts b/platforms/ereputation/api/src/controllers/ReferenceController.ts index 0588d20ab..abc3f35a6 100644 --- a/platforms/ereputation/api/src/controllers/ReferenceController.ts +++ b/platforms/ereputation/api/src/controllers/ReferenceController.ts @@ -79,12 +79,21 @@ export class ReferenceController { */ getAllReferences = async (req: Request, res: Response) => { const apiKey = process.env.VISUALIZER_API_KEY; - if (apiKey && req.headers['x-visualizer-key'] !== apiKey) { + if (!apiKey) { + return res.status(500).json({ error: 'Server misconfiguration: VISUALIZER_API_KEY is not set' }); + } + if (req.headers['x-visualizer-key'] !== apiKey) { return res.status(401).json({ error: 'Unauthorized' }); } try { - const references = await this.referenceService.getAllReferences(); + const MAX_LIMIT = 500; + const rawLimit = parseInt(req.query.limit as string, 10); + const rawOffset = parseInt(req.query.offset as string, 10); + const limit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, MAX_LIMIT) : MAX_LIMIT; + const offset = Number.isFinite(rawOffset) && rawOffset >= 0 ? rawOffset : 0; + + const references = await this.referenceService.getAllReferences(limit, offset); res.json({ references: references.map(ref => ({