From f644aa0ddf9914c96590137b9b4667225aeb796b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:46:51 +0100 Subject: [PATCH 01/27] chore(deps): bump @tanstack/react-query-persist-client (#3118) Bumps [@tanstack/react-query-persist-client](https://github.com/TanStack/query/tree/HEAD/packages/react-query-persist-client) from 5.66.0 to 5.66.9. - [Release notes](https://github.com/TanStack/query/releases) - [Commits](https://github.com/TanStack/query/commits/v5.66.9/packages/react-query-persist-client) --- updated-dependencies: - dependency-name: "@tanstack/react-query-persist-client" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../exchange-oracle/client/package.json | 2 +- .../apps/job-launcher/client/package.json | 2 +- packages/apps/staking/package.json | 2 +- yarn.lock | 28 +++++++++++++------ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json index 30f1b25d4b..23ebd79cfe 100644 --- a/packages/apps/fortune/exchange-oracle/client/package.json +++ b/packages/apps/fortune/exchange-oracle/client/package.json @@ -30,7 +30,7 @@ "@mui/material": "^5.16.7", "@tanstack/query-sync-storage-persister": "^5.59.0", "@tanstack/react-query": "^5.60.5", - "@tanstack/react-query-persist-client": "^5.51.11", + "@tanstack/react-query-persist-client": "^5.66.9", "axios": "^1.7.2", "ethers": "^6.13.5", "react": "^18.3.1", diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 8826196ad9..099ef4b1d6 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -16,7 +16,7 @@ "@stripe/stripe-js": "^4.2.0", "@tanstack/query-sync-storage-persister": "^5.59.0", "@tanstack/react-query": "^5.60.5", - "@tanstack/react-query-persist-client": "^5.51.11", + "@tanstack/react-query-persist-client": "^5.66.9", "axios": "^1.1.3", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.12", diff --git a/packages/apps/staking/package.json b/packages/apps/staking/package.json index ef20c96999..b6ac6e8c25 100644 --- a/packages/apps/staking/package.json +++ b/packages/apps/staking/package.json @@ -32,7 +32,7 @@ "@mui/material": "^5.16.7", "@tanstack/query-sync-storage-persister": "^5.59.0", "@tanstack/react-query": "^5.60.5", - "@tanstack/react-query-persist-client": "^5.51.11", + "@tanstack/react-query-persist-client": "^5.66.9", "axios": "^1.7.2", "ethers": "^6.13.5", "react": "^18.3.1", diff --git a/yarn.lock b/yarn.lock index fc899da0e4..5e348e1f55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5924,6 +5924,11 @@ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.66.0.tgz#163f670b3b4e3b3cdbff6698ad44b2edfcaed185" integrity sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw== +"@tanstack/query-core@5.66.4": + version "5.66.4" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.66.4.tgz#44b87bff289466adbfa0de8daa5756cbd2d61c61" + integrity sha512-skM/gzNX4shPkqmdTCSoHtJAPMTtmIJNS0hE+xwTTUVYwezArCT34NMermABmBVUg5Ls5aiUXEDXfqwR1oVkcA== + "@tanstack/query-devtools@5.65.0": version "5.65.0" resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.65.0.tgz#37da5e911543b4f6d98b9a04369eab0de6044ba1" @@ -5936,6 +5941,13 @@ dependencies: "@tanstack/query-core" "5.66.0" +"@tanstack/query-persist-client-core@5.66.4": + version "5.66.4" + resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.66.4.tgz#5c0802d5e88ee5e372d7cbb82e51f8f92261d8c6" + integrity sha512-OAw98P8DhrZyxZ60Tz4Eq8KyEAKPWIf3tuSStzS0F3jovW42/RpFy7wmgg3SrOgI7/kVKspBvPQDQn6ymcpwCg== + dependencies: + "@tanstack/query-core" "5.66.4" + "@tanstack/query-sync-storage-persister@^5.59.0": version "5.66.0" resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.66.0.tgz#ca92e68bd17ca102d4eaaa131b6a1a9be6060480" @@ -5951,12 +5963,12 @@ dependencies: "@tanstack/query-devtools" "5.65.0" -"@tanstack/react-query-persist-client@^5.51.11": - version "5.66.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.66.0.tgz#9b6f9b349ad20aacc58ebc747fbc4c33605618ff" - integrity sha512-LpocQrJF9HCR6N09X7+k8TjkgYsVHPB4e4AvV2qvCeL5aOsQWORttEqCHldohnJwdVm/68tWacw2jVP/8AuI1w== +"@tanstack/react-query-persist-client@^5.66.9": + version "5.66.9" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.66.9.tgz#0aa8f92c185cb96fd5517f4eaf4cb52f6df2ae01" + integrity sha512-ipuTUUKqbO2F6QHKlxvKO62ar1I13JrxyPYb4Je6jEPlrm7vh8hhfmXmqIdsyle9aRQsJCe6VGPj9igATDhMlA== dependencies: - "@tanstack/query-persist-client-core" "5.66.0" + "@tanstack/query-persist-client-core" "5.66.4" "@tanstack/react-query@^5.48.0", "@tanstack/react-query@^5.60.5", "@tanstack/react-query@^5.61.0": version "5.66.0" @@ -19839,9 +19851,9 @@ victory-vendor@^36.6.8: d3-timer "^3.0.1" viem@2.7.14, viem@^2.15.1: - version "2.23.3" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.23.3.tgz#3b8af9490f8f453a17e849d774bea1b5c992738c" - integrity sha512-ON/Uybteajqxn3iFyhV/6Ybm+QKhcrsVyTZf/9v2w0CvYQIoyJYCfHSsQR9zpsbOGrR7d2p62w6jzb6fqzzacg== + version "2.23.5" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.23.5.tgz#50fb9ea0701d58e6a7a1714ecaa5edfa100bb391" + integrity sha512-cUfBHdFQHmBlPW0loFXda0uZcoU+uJw3NRYQRwYgkrpH6PgovH8iuVqDn6t1jZk82zny4wQL54c9dCX2W9kLMg== dependencies: "@noble/curves" "1.8.1" "@noble/hashes" "1.7.1" From abf31c2d1ae453c4014af28b7ad705b7776744a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Wed, 26 Feb 2025 09:04:33 +0100 Subject: [PATCH 02/27] refactor: remove end-to-end testing configurations and related files (#3126) --- packages/apps/dashboard/server/.env.example | 3 - packages/apps/dashboard/server/README.md | 3 - packages/apps/dashboard/server/package.json | 3 +- .../config/test-environment-config.service.ts | 20 - .../dashboard/server/test/app.e2e-spec.ts | 68 --- .../apps/dashboard/server/test/jest-e2e.json | 12 - .../fortune/exchange-oracle/server/README.md | 3 - .../exchange-oracle/server/package.json | 1 - .../server/test/app.e2e-spec.ts | 24 - .../exchange-oracle/server/test/jest-e2e.json | 9 - .../apps/fortune/recording-oracle/README.md | 3 - .../fortune/recording-oracle/package.json | 1 - .../recording-oracle/test/app.e2e-spec.ts | 24 - .../recording-oracle/test/jest-e2e.json | 9 - packages/apps/job-launcher/server/README.md | 3 - .../apps/job-launcher/server/package.json | 1 - .../job-launcher/server/src/app.module.ts | 14 +- .../server/src/common/constants/index.ts | 2 - .../server/test/e2e/app.e2e-spec.ts | 52 -- .../server/test/e2e/auth-workflow.e2e-spec.ts | 145 ----- .../server/test/e2e/cvat-workflow.e2e-spec.ts | 549 ----------------- .../job-launcher/server/test/e2e/env-setup.ts | 69 --- .../fiat-account-deposit-workflow.e2e-spec.ts | 112 ---- .../test/e2e/fortune-workflow.e2e-spec.ts | 197 ------ .../test/e2e/hcaptcha-workflow.e2e-spec.ts | 572 ------------------ .../e2e/quick-launch-workflow.e2e-spec.ts | 196 ------ .../test/e2e/restore-password.e2e-spec.ts | 198 ------ .../job-launcher/server/test/e2e/utils.ts | 8 - .../job-launcher/server/test/jest-e2e.json | 9 - 29 files changed, 5 insertions(+), 2305 deletions(-) delete mode 100644 packages/apps/dashboard/server/src/common/config/test-environment-config.service.ts delete mode 100644 packages/apps/dashboard/server/test/app.e2e-spec.ts delete mode 100644 packages/apps/dashboard/server/test/jest-e2e.json delete mode 100644 packages/apps/fortune/exchange-oracle/server/test/app.e2e-spec.ts delete mode 100644 packages/apps/fortune/exchange-oracle/server/test/jest-e2e.json delete mode 100644 packages/apps/fortune/recording-oracle/test/app.e2e-spec.ts delete mode 100644 packages/apps/fortune/recording-oracle/test/jest-e2e.json delete mode 100644 packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/cvat-workflow.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/env-setup.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/fiat-account-deposit-workflow.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/fortune-workflow.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/hcaptcha-workflow.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts delete mode 100644 packages/apps/job-launcher/server/test/e2e/utils.ts delete mode 100644 packages/apps/job-launcher/server/test/jest-e2e.json diff --git a/packages/apps/dashboard/server/.env.example b/packages/apps/dashboard/server/.env.example index d8532b39e4..f3ac7f4f39 100644 --- a/packages/apps/dashboard/server/.env.example +++ b/packages/apps/dashboard/server/.env.example @@ -34,6 +34,3 @@ S3_BUCKET= #Web3 WEB3_ENV= RPC_URL_POLYGON= - -# e2e testing -E2E_TESTING_EMAIL_ADDRESS= diff --git a/packages/apps/dashboard/server/README.md b/packages/apps/dashboard/server/README.md index 83729419a3..efbadeb8a0 100644 --- a/packages/apps/dashboard/server/README.md +++ b/packages/apps/dashboard/server/README.md @@ -51,9 +51,6 @@ $ yarn run start:prod # unit tests $ yarn run test -# e2e tests -$ yarn run test:e2e - # test coverage $ yarn run test:cov ``` diff --git a/packages/apps/dashboard/server/package.json b/packages/apps/dashboard/server/package.json index 0822378082..1b3bf5252c 100644 --- a/packages/apps/dashboard/server/package.json +++ b/packages/apps/dashboard/server/package.json @@ -16,8 +16,7 @@ "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" }, "dependencies": { "@human-protocol/sdk": "*", diff --git a/packages/apps/dashboard/server/src/common/config/test-environment-config.service.ts b/packages/apps/dashboard/server/src/common/config/test-environment-config.service.ts deleted file mode 100644 index fbcd16506f..0000000000 --- a/packages/apps/dashboard/server/src/common/config/test-environment-config.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Joi from 'joi'; -import { ConfigService } from '@nestjs/config'; -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class TestEnvironmentConfigService { - constructor(private configService: ConfigService) {} - - /** - * The email address used for end-to-end (E2E) testing. - * Default: empty string - */ - get e2eTestingEmailAddress(): string { - return this.configService.get('E2E_TESTING_EMAIL_ADDRESS', ''); - } -} - -export const testEnvValidator = Joi.object({ - E2E_TESTING_EMAIL_ADDRESS: Joi.string().required(), -}); diff --git a/packages/apps/dashboard/server/test/app.e2e-spec.ts b/packages/apps/dashboard/server/test/app.e2e-spec.ts deleted file mode 100644 index 35f4ae9f85..0000000000 --- a/packages/apps/dashboard/server/test/app.e2e-spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import request from 'supertest'; -import { AppModule } from '../src/app.module'; -import { beforeAll, afterAll, describe, it, expect } from '@jest/globals'; -import { ConfigService } from '@nestjs/config'; -import { - TestEnvironmentConfigService, - testEnvValidator, -} from '../src/common/config/test-environment-config.service'; -import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { Cache } from 'cache-manager'; - -describe('Dashboard (e2e) tests', () => { - let app: INestApplication; - let configService: ConfigService; - let envConfigService: TestEnvironmentConfigService; - let cacheManager: Cache; - - beforeAll(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - configService = moduleFixture.get(ConfigService); - envConfigService = new TestEnvironmentConfigService(configService); - cacheManager = app.get(CACHE_MANAGER); - - const { error } = testEnvValidator.validate({ - E2E_TESTING_EMAIL_ADDRESS: envConfigService.e2eTestingEmailAddress, - }); - - if (error) { - throw new Error(`Test environment is not valid: ${error.message}`); - } - - await app.init(); - }); - - describe('Networks API', () => { - it('should successfully retrieve operating networks and validate chain IDs', async () => { - const response = await request(app.getHttpServer()) - .get('/networks/operating') - .expect(200); - - expect(Array.isArray(response.body)).toBe(true); - expect(response.body.every((id: any) => typeof id === 'number')).toBe( - true, - ); - }); - - it('should retrieve from cache when it exists', async () => { - const mockData = [1, 2, 3]; - await cacheManager.set('operating-networks', mockData); - - const response = await request(app.getHttpServer()) - .get('/networks/operating') - .expect(200); - - expect(response.body).toEqual(mockData); - }); - }); - - afterAll(async () => { - await app.close(); - }); -}); diff --git a/packages/apps/dashboard/server/test/jest-e2e.json b/packages/apps/dashboard/server/test/jest-e2e.json deleted file mode 100644 index d530a8f8d9..0000000000 --- a/packages/apps/dashboard/server/test/jest-e2e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "transformIgnorePatterns": [ - "/node_modules/(?!(@nestjs/config|uuid)).+\\.js$" - ] -} diff --git a/packages/apps/fortune/exchange-oracle/server/README.md b/packages/apps/fortune/exchange-oracle/server/README.md index 92a7f6b570..3a4ce72618 100644 --- a/packages/apps/fortune/exchange-oracle/server/README.md +++ b/packages/apps/fortune/exchange-oracle/server/README.md @@ -90,9 +90,6 @@ $ yarn run start:debug # unit tests $ yarn run test -# e2e tests -$ yarn run test:e2e - # test coverage $ yarn run test:cov ``` diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index 09b216cac0..99a18ac957 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -23,7 +23,6 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", "setup:local": "ts-node ./test/setup.ts", "generate-env-doc": "ts-node scripts/generate-env-doc.ts" }, diff --git a/packages/apps/fortune/exchange-oracle/server/test/app.e2e-spec.ts b/packages/apps/fortune/exchange-oracle/server/test/app.e2e-spec.ts deleted file mode 100644 index 50cda62332..0000000000 --- a/packages/apps/fortune/exchange-oracle/server/test/app.e2e-spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); -}); diff --git a/packages/apps/fortune/exchange-oracle/server/test/jest-e2e.json b/packages/apps/fortune/exchange-oracle/server/test/jest-e2e.json deleted file mode 100644 index e9d912f3e3..0000000000 --- a/packages/apps/fortune/exchange-oracle/server/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/packages/apps/fortune/recording-oracle/README.md b/packages/apps/fortune/recording-oracle/README.md index e8d16bd3c5..c84ac39290 100644 --- a/packages/apps/fortune/recording-oracle/README.md +++ b/packages/apps/fortune/recording-oracle/README.md @@ -60,9 +60,6 @@ $ yarn run start:debug # unit tests $ yarn run test -# e2e tests -$ yarn run test:e2e - # test coverage $ yarn run test:cov ``` diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json index 6e0f294dfe..2dd1dc7c58 100644 --- a/packages/apps/fortune/recording-oracle/package.json +++ b/packages/apps/fortune/recording-oracle/package.json @@ -16,7 +16,6 @@ "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", - "test:e2e": "jest --config ./test/jest-e2e.json", "postgres": "docker compose up -d postgres", "docker": "docker compose up -d", "local": "docker compose down && (concurrently --hide 0 \"yarn docker\" \"yarn recording-oracle:dev\" )", diff --git a/packages/apps/fortune/recording-oracle/test/app.e2e-spec.ts b/packages/apps/fortune/recording-oracle/test/app.e2e-spec.ts deleted file mode 100644 index 50cda62332..0000000000 --- a/packages/apps/fortune/recording-oracle/test/app.e2e-spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); -}); diff --git a/packages/apps/fortune/recording-oracle/test/jest-e2e.json b/packages/apps/fortune/recording-oracle/test/jest-e2e.json deleted file mode 100644 index e9d912f3e3..0000000000 --- a/packages/apps/fortune/recording-oracle/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/packages/apps/job-launcher/server/README.md b/packages/apps/job-launcher/server/README.md index c409c3f656..c9a5b013ca 100644 --- a/packages/apps/job-launcher/server/README.md +++ b/packages/apps/job-launcher/server/README.md @@ -88,9 +88,6 @@ $ yarn run start:debug # unit tests $ yarn run test -# e2e tests -$ yarn run test:e2e - # test coverage $ yarn run test:cov ``` diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index e6d7cdbccd..e9b7c083a0 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -25,7 +25,6 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "export POSTGRES_DATABASE=job-launcher POSTGRES_PASSWORD=qwerty POSTGRES_USER=default POSTGRES_HOST=0.0.0.0 NODE_ENV=test-e2e && docker compose up -d postgres && yarn migration:run && jest --config ./test/jest-e2e.json; testStatus=$?; docker rm -f postgres; exit $testStatus", "generate-env-doc": "ts-node scripts/generate-env-doc.ts" }, "dependencies": { diff --git a/packages/apps/job-launcher/server/src/app.module.ts b/packages/apps/job-launcher/server/src/app.module.ts index e6fbbcda18..322ef389d1 100644 --- a/packages/apps/job-launcher/server/src/app.module.ts +++ b/packages/apps/job-launcher/server/src/app.module.ts @@ -19,7 +19,6 @@ import { CronJobModule } from './modules/cron-job/cron-job.module'; import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; import { WebhookModule } from './modules/webhook/webhook.module'; import { EnvConfigModule } from './common/config/config.module'; -import { E2E_TEST_ENV } from './common/constants'; import { ExceptionFilter } from './common/exceptions/exception.filter'; import { ScheduleModule } from '@nestjs/schedule'; import { StatisticModule } from './modules/statistic/statistic.module'; @@ -52,15 +51,10 @@ import { TransformEnumInterceptor } from './common/interceptors/transform-enum.i imports: [ ScheduleModule.forRoot(), ConfigModule.forRoot({ - ignoreEnvFile: process.env.NODE_ENV === E2E_TEST_ENV, - ...(process.env.NODE_ENV !== E2E_TEST_ENV && { - envFilePath: process.env.NODE_ENV - ? `.env.${process.env.NODE_ENV as string}` - : '.env', - }), - ...(process.env.NODE_ENV !== E2E_TEST_ENV && { - validationSchema: envValidator, - }), + envFilePath: process.env.NODE_ENV + ? `.env.${process.env.NODE_ENV as string}` + : '.env', + validationSchema: envValidator, }), DatabaseModule, HealthModule, diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index a34772c2bd..64c81444bd 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -67,6 +67,4 @@ export const HCAPTCHA_NOT_PRESENTED_LABEL = 'Not presented'; export const RESEND_EMAIL_VERIFICATION_PATH = '/auth/resend-email-verification'; export const LOGOUT_PATH = '/auth/logout'; -export const E2E_TEST_ENV = 'test-e2e'; - export const MUTEX_TIMEOUT = 2000; //ms diff --git a/packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts deleted file mode 100644 index f23cf97317..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import request from 'supertest'; -import { AppModule } from '../../src/app.module'; -import setupE2eEnvironment from './env-setup'; -import { ChainId } from '@human-protocol/sdk'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; -import { - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../../test/constants'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeAll(async () => { - setupE2eEnvironment(); - }); - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(301) - .expect('Moved Permanently. Redirecting to /swagger'); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts deleted file mode 100644 index 1c7065e852..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserRepository } from '../../src/modules/user/user.repository'; -import { TokenRepository } from '../../src/modules/auth/token.repository'; -import { TokenType } from '../../src/modules/auth/token.entity'; -import { UserStatus } from '../../src/common/enums/user'; -import setupE2eEnvironment from './env-setup'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; -import { ChainId } from '@human-protocol/sdk'; -import { - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; - -describe('AuthService E2E', () => { - let app: INestApplication; - let userRepository: UserRepository; - let tokenRepository: TokenRepository; - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - userRepository = moduleFixture.get(UserRepository); - tokenRepository = moduleFixture.get(TokenRepository); - }); - - afterAll(async () => { - await app.close(); - }); - - it.only('should test authentication workflow', async () => { - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - const password = 'Password1!'; - const hCaptchaToken = 'string'; - - // User Registration - const signUpResponse = await request(app.getHttpServer()) - .post('/auth/signup') - .send({ - email, - password, - h_captcha_token: hCaptchaToken, - }); - expect(signUpResponse.status).toBe(201); - - // Verify user registration - const userEntity = await userRepository.findByEmail(email); - expect(userEntity).toBeDefined(); - - // Email Verification - expect(userEntity!.status).toBe(UserStatus.PENDING); - - // Invalid verification - const invalidVerifyTokenResponse = await request(app.getHttpServer()) - .post(`/auth/email-verification`) - .send({ token: 123 }); - expect(invalidVerifyTokenResponse.status).toBe(400); - - const tokenEntity = await tokenRepository.findOneByUserIdAndType( - userEntity!.id, - TokenType.EMAIL, - ); - - // Valid verification - const verifyTokenResponse = await request(app.getHttpServer()) - .post(`/auth/email-verification`) - .send({ token: tokenEntity!.uuid }); - - expect(verifyTokenResponse.status).toBe(200); - - const updatedUserEntity = await userRepository.findByEmail(email); - expect(updatedUserEntity!.status).toBe(UserStatus.ACTIVE); - - // User Authentication - const signInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password, - h_captcha_token: hCaptchaToken, - }); - expect(signInResponse.status).toBe(200); - - const refreshedUserEntity = await userRepository.findByEmail(email); - const refreshTokenEntity = await tokenRepository.findOneByUserIdAndType( - refreshedUserEntity!.id, - TokenType.REFRESH, - ); - expect(refreshTokenEntity).toBeDefined(); - - // Invalid Authentication - const invalidSignInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password: 'Incorrect', - h_captcha_token: hCaptchaToken, - }); - expect(invalidSignInResponse.status).toBe(403); - - // Refresh Token - const refreshTokenResponse = await request(app.getHttpServer()) - .post('/auth/refresh') - .send({ refresh_token: refreshTokenEntity!.uuid }); // Add appropriate data if needed - - expect(refreshTokenResponse.status).toBe(200); - expect(refreshTokenResponse.body).toHaveProperty('access_token'); - expect(refreshTokenResponse.body).toHaveProperty('refresh_token'); - - const accessToken = refreshTokenResponse.body.access_token; - - // Resend Email Verification - const resendEmailVerificationResponse = await request(app.getHttpServer()) - .post('/auth/resend-email-verification') - .set('Authorization', `Bearer ${accessToken}`) - .send({ email }); - expect(resendEmailVerificationResponse.status).toBe(204); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/cvat-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/cvat-workflow.e2e-spec.ts deleted file mode 100644 index 1379c8389b..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/cvat-workflow.e2e-spec.ts +++ /dev/null @@ -1,549 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { HttpStatus, INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserStatus } from '../../src/common/enums/user'; -import { UserService } from '../../src/modules/user/user.service'; -import { UserEntity } from '../../src/modules/user/user.entity'; -import setupE2eEnvironment from './env-setup'; -import { - MOCK_CVAT_DATA, - MOCK_CVAT_GT, - MOCK_CVAT_LABELS, - MOCK_FILE_URL, - MOCK_REQUESTER_DESCRIPTION, - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; -import { JobRequestType, JobStatus } from '../../src/common/enums/job'; -import { ErrorJob } from '../../src/common/constants/errors'; -import { - Currency, - PaymentSource, - PaymentStatus, - PaymentType, - TokenId, -} from '../../src/common/enums/payment'; -import { PaymentEntity } from '../../src/modules/payment/payment.entity'; -import { PaymentRepository } from '../../src/modules/payment/payment.repository'; -import { JobRepository } from '../../src/modules/job/job.repository'; -import { AWSRegions, StorageProviders } from '../../src/common/enums/storage'; -import { ChainId } from '@human-protocol/sdk'; -import { StorageService } from '../../src/modules/storage/storage.service'; -import { delay, getFileNameFromURL } from './utils'; -import { PaymentService } from '../../src/modules/payment/payment.service'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; - -describe('CVAT E2E workflow', () => { - let app: INestApplication; - let paymentRepository: PaymentRepository; - let jobRepository: JobRepository; - let userService: UserService; - let storageService: StorageService; - let paymentService: PaymentService; - - let userEntity: UserEntity; - let accessToken: string; - let paidAmount = 0; - const initialBalance = 100; - - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - const paymentIntentId = crypto.randomBytes(16).toString('hex'); - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - paymentRepository = moduleFixture.get(PaymentRepository); - jobRepository = moduleFixture.get(JobRepository); - userService = moduleFixture.get(UserService); - storageService = moduleFixture.get(StorageService); - paymentService = moduleFixture.get(PaymentService); - - userEntity = await userService.create({ - email, - password: 'Password1!', - hCaptchaToken: 'string', - }); - - userEntity.status = UserStatus.ACTIVE; - await userEntity.save(); - - const signInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password: 'Password1!', - h_captcha_token: 'string', - }); - - accessToken = signInResponse.body.access_token; - - const newPaymentEntity = new PaymentEntity(); - Object.assign(newPaymentEntity, { - userId: userEntity.id, - source: PaymentSource.FIAT, - type: PaymentType.DEPOSIT, - amount: initialBalance, - currency: Currency.USD, - rate: 1, - transaction: paymentIntentId, - status: PaymentStatus.SUCCEEDED, - }); - await paymentRepository.createUnique(newPaymentEntity); - }); - - afterEach(async () => { - // Add a delay of 1 second between each test. Prevention: "429 Too Many Requests" - await delay(1000); - }); - - afterAll(async () => { - await app.close(); - }); - - it('should create an CVAT job with image boxes type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const groundTruthsData = MOCK_CVAT_GT; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(groundTruths.url), - }, - type: JobRequestType.IMAGE_BOXES, - fund_amount: 10, - }; - - const response = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(manifest.job_bounty).toBeDefined(); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an CVAT job with image points type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const groundTruthsData = MOCK_CVAT_GT; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(groundTruths.url), - }, - type: JobRequestType.IMAGE_POINTS, - fund_amount: 10, - }; - - const response = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(manifest.job_bounty).toBeDefined(); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an CVAT job with image boxes from points type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const datasetData = MOCK_CVAT_DATA; - - const dataset = await storageService.uploadJsonLikeData(datasetData); - - const groundTruthsData = MOCK_CVAT_GT; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - points: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(dataset.url), - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(groundTruths.url), - }, - type: JobRequestType.IMAGE_BOXES_FROM_POINTS, - fund_amount: 10, - }; - - const response = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(manifest.job_bounty).toBeDefined(); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should handle when data does not exist while creating a CVAT job with image boxes from points type', async () => { - const groundTruthsData = MOCK_CVAT_GT; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(groundTruths.url), - }, - type: JobRequestType.IMAGE_BOXES_FROM_POINTS, - fund_amount: 10, - }; - - const invalidCreateJobResponse = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(409); - - expect(invalidCreateJobResponse.status).toBe(HttpStatus.CONFLICT); - expect(invalidCreateJobResponse.body.message).toBe(ErrorJob.DataNotExist); - }); - - it('should create an CVAT job with image skeletons from boxes type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const datasetData = MOCK_CVAT_DATA; - - const dataset = await storageService.uploadJsonLikeData(datasetData); - - const groundTruthsData = MOCK_CVAT_GT; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - boxes: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(dataset.url), - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(groundTruths.url), - }, - type: JobRequestType.IMAGE_SKELETONS_FROM_BOXES, - fund_amount: 10, - }; - - const response = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(manifest.job_bounty).toBeDefined(); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should handle when data does not exist while creating a CVAT job with image skeletons from boxes type', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const groundTruthsData = MOCK_CVAT_GT; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: getFileNameFromURL(groundTruths.url), - }, - type: JobRequestType.IMAGE_BOXES_FROM_POINTS, - fund_amount: 10, - }; - - const invalidCreateJobResponse = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(409); - - expect(invalidCreateJobResponse.status).toBe(HttpStatus.CONFLICT); - expect(invalidCreateJobResponse.body.message).toBe(ErrorJob.DataNotExist); - }); - - it('should handle not enough funds error', async () => { - const cvatDto = { - chain_id: ChainId.LOCALHOST, - data: { - dataset: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - }, - labels: MOCK_CVAT_LABELS, - requester_description: MOCK_REQUESTER_DESCRIPTION, - user_guide: MOCK_FILE_URL, - min_quality: 0.8, - ground_truth: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: MOCK_FILE_URL, - }, - type: JobRequestType.IMAGE_BOXES, - fund_amount: 100000000, - }; - - const invalidCreateJobResponse = await request(app.getHttpServer()) - .post('/job/cvat') - .set('Authorization', `Bearer ${accessToken}`) - .send(cvatDto) - .expect(400); - - expect(invalidCreateJobResponse.status).toBe(HttpStatus.BAD_REQUEST); - expect(invalidCreateJobResponse.body.message).toBe(ErrorJob.NotEnoughFunds); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/env-setup.ts b/packages/apps/job-launcher/server/test/e2e/env-setup.ts deleted file mode 100644 index 8dec352553..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/env-setup.ts +++ /dev/null @@ -1,69 +0,0 @@ -export default function setupE2eEnvironment() { - process.env.NODE_ENV = 'test-e2e'; - process.env.S3_ACCESS_KEY = 'value'; - - process.env.POSTGRES_DATABASE = 'job-launcher'; - process.env.POSTGRES_PASSWORD = 'qwerty'; - process.env.POSTGRES_USER = 'default'; - process.env.POSTGRES_HOST = '0.0.0.0'; - - process.env.SESSION_SECRET = 'test'; - process.env.POSTGRES_HOST = '0.0.0.0'; - process.env.POSTGRES_USER = 'default'; - process.env.POSTGRES_PASSWORD = 'qwerty'; - process.env.POSTGRES_DATABASE = 'job-launcher'; - process.env.POSTGRES_PORT = '5432'; - process.env.POSTGRES_SSL = 'false'; - - process.env.WEB3_ENV = 'localhost'; - process.env.WEB3_PRIVATE_KEY = - '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; - process.env.GAS_PRICE_MULTIPLIER = '1'; - process.env.PGP_ENCRYPT = 'false'; - process.env.FORTUNE_EXCHANGE_ORACLE_ADDRESS = - '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'; - process.env.FORTUNE_RECORDING_ORACLE_ADDRESS = - '0x90F79bf6EB2c4f870365E785982E1f101E93b906'; - process.env.CVAT_EXCHANGE_ORACLE_ADDRESS = - '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'; - process.env.CVAT_RECORDING_ORACLE_ADDRESS = - '0x90F79bf6EB2c4f870365E785982E1f101E93b906'; - process.env.REPUTATION_ORACLE_ADDRESS = - '0x90F79bf6EB2c4f870365E785982E1f101E93b906'; - process.env.HCAPTCHA_RECORDING_ORACLE_URI = 'a'; - process.env.HCAPTCHA_REPUTATION_ORACLE_URI = 'a'; - process.env.HCAPTCHA_ORACLE_ADDRESS = 'a'; - process.env.HCAPTCHA_SITE_KEY = 'a'; - - process.env.HASH_SECRET = 'a328af3fc1dad15342cc3d68936008fa'; - process.env.JWT_PRIVATE_KEY = `-----BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEID2jVcOtjupW4yqNTz70nvmt1GSvqET5G7lpC0Gp31LFoAoGCCqGSM49 -AwEHoUQDQgAEUznVCoagfRCuMA3TfG51xWShNrMJt86lkzfAep9bfBxbaCBbUhJ1 -s9+9eeLMG/nUMAaGxWeOwJ92L/KvzN6RFw== ------END EC PRIVATE KEY-----`; - - process.env.JWT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUznVCoagfRCuMA3TfG51xWShNrMJ -t86lkzfAep9bfBxbaCBbUhJ1s9+9eeLMG/nUMAaGxWeOwJ92L/KvzN6RFw== ------END PUBLIC KEY-----`; - process.env.JWT_ACCESS_TOKEN_EXPIRES_IN = '600'; - process.env.JWT_REFRESH_TOKEN_EXPIRES_IN = '1d'; - - process.env.S3_ENDPOINT = 'localhost'; - process.env.S3_PORT = '9000'; - process.env.S3_ACCESS_KEY = 'access-key'; - process.env.S3_SECRET_KEY = 'secret-key'; - process.env.S3_BUCKET = 'bucket'; - process.env.S3_USE_SSL = 'false'; - - process.env.STRIPE_API_VERSION = '2022-11-15'; - process.env.STRIPE_APP_NAME = 'Staging Launcher Server'; - process.env.STRIPE_APP_VERSION = '1.0.0'; - process.env.STRIPE_APP_INFO_URL = - 'https://github.com/humanprotocol/human-protocol/tree/main/packages/apps/job-launcher/server'; - - process.env.SENDGRID_API_KEY = 'sendgrid-disabled'; -} diff --git a/packages/apps/job-launcher/server/test/e2e/fiat-account-deposit-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/fiat-account-deposit-workflow.e2e-spec.ts deleted file mode 100644 index 07dafa08b3..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/fiat-account-deposit-workflow.e2e-spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserStatus } from '../../src/common/enums/user'; -import { UserService } from '../../src/modules/user/user.service'; -import { UserEntity } from '../../src/modules/user/user.entity'; -import setupE2eEnvironment from './env-setup'; -import { - Currency, - PaymentSource, - PaymentStatus, - PaymentType, -} from '../../src/common/enums/payment'; -import { PaymentRepository } from '../../src/modules/payment/payment.repository'; -import { ChainId } from '@human-protocol/sdk'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; -import { - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; - -describe('Fiat account deposit E2E workflow', () => { - let app: INestApplication; - let paymentRepository: PaymentRepository; - let userService: UserService; - - let userEntity: UserEntity; - let accessToken: string; - - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - paymentRepository = moduleFixture.get(PaymentRepository); - userService = moduleFixture.get(UserService); - - userEntity = await userService.create({ - email, - password: 'Password1!', - hCaptchaToken: 'string', - }); - - userEntity.status = UserStatus.ACTIVE; - await userEntity.save(); - - const signInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password: 'Password1!', - h_captcha_token: 'string', - }); - - accessToken = signInResponse.body.access_token; - }); - - afterAll(async () => { - await app.close(); - }); - - it('should create a fiat payment successfully', async () => { - const payemntFiatDto = { - amount: 10, - currency: Currency.USD, - }; - - const response = await request(app.getHttpServer()) - .post('/payment/fiat') - .set('Authorization', `Bearer ${accessToken}`) - .send(payemntFiatDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.PENDING, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.DEPOSIT); - expect(paymentEntities[0].source).toBe(PaymentSource.FIAT); - expect(paymentEntities[0].currency).toBe(Currency.USD); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/fortune-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/fortune-workflow.e2e-spec.ts deleted file mode 100644 index 0f40299e65..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/fortune-workflow.e2e-spec.ts +++ /dev/null @@ -1,197 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { HttpStatus, INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserStatus } from '../../src/common/enums/user'; -import { UserService } from '../../src/modules/user/user.service'; -import { UserEntity } from '../../src/modules/user/user.entity'; -import setupE2eEnvironment from './env-setup'; -import { JobRequestType, JobStatus } from '../../src/common/enums/job'; -import { ChainId } from '@human-protocol/sdk'; -import { ErrorJob } from '../../src/common/constants/errors'; -import { - Currency, - PaymentSource, - PaymentStatus, - PaymentType, - TokenId, -} from '../../src/common/enums/payment'; -import { PaymentEntity } from '../../src/modules/payment/payment.entity'; -import { PaymentRepository } from '../../src/modules/payment/payment.repository'; -import { JobRepository } from '../../src/modules/job/job.repository'; -import { StorageService } from '../../src/modules/storage/storage.service'; -import { delay } from './utils'; -import { PaymentService } from '../../src/modules/payment/payment.service'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; -import { - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; - -describe('Fortune E2E workflow', () => { - let app: INestApplication; - let paymentRepository: PaymentRepository; - let jobRepository: JobRepository; - let userService: UserService; - let paymentService: PaymentService; - let storageService: StorageService; - - let userEntity: UserEntity; - let accessToken: string; - const initialBalance = 100; - - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - const paymentIntentId = crypto.randomBytes(16).toString('hex'); - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - paymentRepository = moduleFixture.get(PaymentRepository); - jobRepository = moduleFixture.get(JobRepository); - userService = moduleFixture.get(UserService); - paymentService = moduleFixture.get(PaymentService); - storageService = moduleFixture.get(StorageService); - - userEntity = await userService.create({ - email, - password: 'Password1!', - hCaptchaToken: 'string', - }); - - userEntity.status = UserStatus.ACTIVE; - await userEntity.save(); - - const signInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password: 'Password1!', - h_captcha_token: 'string', - }); - - accessToken = signInResponse.body.access_token; - - const newPaymentEntity = new PaymentEntity(); - Object.assign(newPaymentEntity, { - userId: userEntity.id, - source: PaymentSource.FIAT, - type: PaymentType.DEPOSIT, - amount: initialBalance, - currency: Currency.USD, - rate: 1, - transaction: paymentIntentId, - status: PaymentStatus.SUCCEEDED, - }); - await paymentRepository.createUnique(newPaymentEntity); - }); - - afterEach(async () => { - // Add a delay of 1 second between each test. Prevention: "429 Too Many Requests" - await delay(1000); - }); - - afterAll(async () => { - await app.close(); - }); - - it('should create a Fortune job successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance); - - const createJobDto = { - chain_id: ChainId.LOCALHOST, - requester_title: 'Write an odd number', - requester_description: 'Prime number', - submissions_required: 10, - fund_amount: 10, // USD - }; - - const response = await request(app.getHttpServer()) - .post('/job/fortune') - .set('Authorization', `Bearer ${accessToken}`) - .send(createJobDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - expect(manifest).toMatchObject({ - chainId: ChainId.LOCALHOST, - fundAmount: expect.any(Number), - requestType: JobRequestType.FORTUNE, - requesterDescription: createJobDto.requester_description, - requesterTitle: createJobDto.requester_title, - submissionsRequired: createJobDto.submissions_required, - }); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - const paidAmount = paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should handle not enough funds error', async () => { - const createJobDto = { - chain_id: ChainId.LOCALHOST, - requester_title: 'Write an odd number', - requester_description: 'Prime number', - submissions_required: 10, - fund_amount: 100000000, // USD - }; - - const invalidQuickLaunchResponse = await request(app.getHttpServer()) - .post('/job/fortune') - .set('Authorization', `Bearer ${accessToken}`) - .send(createJobDto) - .expect(400); - - expect(invalidQuickLaunchResponse.status).toBe(HttpStatus.BAD_REQUEST); - expect(invalidQuickLaunchResponse.body.message).toBe( - ErrorJob.NotEnoughFunds, - ); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/hcaptcha-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/hcaptcha-workflow.e2e-spec.ts deleted file mode 100644 index 7e93c5ccae..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/hcaptcha-workflow.e2e-spec.ts +++ /dev/null @@ -1,572 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { HttpStatus, INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserStatus } from '../../src/common/enums/user'; -import { UserService } from '../../src/modules/user/user.service'; -import { UserEntity } from '../../src/modules/user/user.entity'; -import setupE2eEnvironment from './env-setup'; -import { - MOCK_BUCKET_FILES, - MOCK_FILE_URL, - MOCK_PRIVATE_KEY, - MOCK_REQUESTER_DESCRIPTION, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; -import { JobCaptchaShapeType, JobStatus } from '../../src/common/enums/job'; -import { ErrorJob } from '../../src/common/constants/errors'; -import { - Currency, - PaymentSource, - PaymentStatus, - PaymentType, - TokenId, -} from '../../src/common/enums/payment'; -import { PaymentEntity } from '../../src/modules/payment/payment.entity'; -import { PaymentRepository } from '../../src/modules/payment/payment.repository'; -import { JobRepository } from '../../src/modules/job/job.repository'; -import { AWSRegions, StorageProviders } from '../../src/common/enums/storage'; -import { ChainId } from '@human-protocol/sdk'; -import { StorageService } from '../../src/modules/storage/storage.service'; -import { delay } from './utils'; -import { PaymentService } from '../../src/modules/payment/payment.service'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; - -describe.skip('hCaptcha E2E workflow', () => { - let app: INestApplication; - let paymentRepository: PaymentRepository; - let jobRepository: JobRepository; - let userService: UserService; - let storageService: StorageService; - let paymentService: PaymentService; - - let userEntity: UserEntity; - let accessToken: string; - let paidAmount = 0; - const initialBalance = 100; - - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - const paymentIntentId = crypto.randomBytes(16).toString('hex'); - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - paymentRepository = moduleFixture.get(PaymentRepository); - jobRepository = moduleFixture.get(JobRepository); - userService = moduleFixture.get(UserService); - storageService = moduleFixture.get(StorageService); - paymentService = moduleFixture.get(PaymentService); - - userEntity = await userService.create({ - email, - password: 'Password1!', - hCaptchaToken: 'string', - }); - - userEntity.status = UserStatus.ACTIVE; - await userEntity.save(); - - const signInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password: 'Password1!', - h_captcha_token: 'string', - }); - - accessToken = signInResponse.body.access_token; - - const newPaymentEntity = new PaymentEntity(); - Object.assign(newPaymentEntity, { - userId: userEntity.id, - source: PaymentSource.FIAT, - type: PaymentType.DEPOSIT, - amount: 100, - currency: Currency.USD, - rate: 1, - transaction: paymentIntentId, - status: PaymentStatus.SUCCEEDED, - }); - await paymentRepository.createUnique(newPaymentEntity); - }); - - afterEach(async () => { - // Add a delay of 1 second between each test. Prevention: "429 Too Many Requests" - await delay(1000); - }); - - afterAll(async () => { - await app.close(); - }); - - it('should create an hCaptcha job with comparison type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - accuracy_target: 0.9, - completion_date: new Date(), - min_requests: 1, - max_requests: 10, - annotations: { - type_of_job: JobCaptchaShapeType.COMPARISON, - labeling_prompt: MOCK_REQUESTER_DESCRIPTION, - ground_truths: MOCK_FILE_URL, - example_images: MOCK_BUCKET_FILES, - task_bid_price: 0.5, - }, - advanced: {}, - }; - - const response = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(manifest).toBeDefined(); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an hCaptcha job with categorization type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const groundTruthsData = { - 'image_name_1.jpg': [['Dog']], - 'image_name_2.jpg': [['Cat']], - }; - - const groundTruths = - await storageService.uploadJsonLikeData(groundTruthsData); - - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - accuracy_target: 0.9, - completion_date: new Date(), - min_requests: 1, - max_requests: 10, - annotations: { - type_of_job: JobCaptchaShapeType.CATEGORIZATION, - labeling_prompt: MOCK_REQUESTER_DESCRIPTION, - ground_truths: groundTruths.url, - example_images: MOCK_BUCKET_FILES, - task_bid_price: 0.5, - }, - advanced: {}, - }; - - const response = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(jobEntity!.manifestUrl).toBeDefined(); - expect(manifest.requester_restricted_answer_set).toMatchObject({ - Dog: { answer_example_uri: 'image_name_1.jpg', en: 'Dog' }, - Cat: { answer_example_uri: 'image_name_2.jpg', en: 'Cat' }, - }); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an hCaptcha job with polygon type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - accuracy_target: 0.9, - completion_date: new Date(), - min_requests: 1, - max_requests: 10, - annotations: { - type_of_job: JobCaptchaShapeType.POLYGON, - label: 'Label', - labeling_prompt: MOCK_REQUESTER_DESCRIPTION, - ground_truths: MOCK_FILE_URL, - example_images: MOCK_BUCKET_FILES, - task_bid_price: 0.5, - }, - advanced: {}, - }; - - const response = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(jobEntity!.manifestUrl).toBeDefined(); - expect(manifest.requester_restricted_answer_set).toMatchObject({ - Label: { en: 'Label' }, - }); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an hCaptcha job with point type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - accuracy_target: 0.9, - completion_date: new Date(), - min_requests: 1, - max_requests: 10, - annotations: { - type_of_job: JobCaptchaShapeType.POINT, - label: 'Label', - labeling_prompt: MOCK_REQUESTER_DESCRIPTION, - ground_truths: MOCK_FILE_URL, - example_images: MOCK_BUCKET_FILES, - task_bid_price: 0.5, - }, - advanced: {}, - }; - - const response = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(jobEntity!.manifestUrl).toBeDefined(); - expect(manifest.requester_restricted_answer_set).toMatchObject({ - Label: { en: 'Label' }, - }); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an hCaptcha job with bounding box type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - accuracy_target: 0.9, - completion_date: new Date(), - min_requests: 1, - max_requests: 10, - annotations: { - type_of_job: JobCaptchaShapeType.BOUNDING_BOX, - label: 'Label', - labeling_prompt: MOCK_REQUESTER_DESCRIPTION, - ground_truths: MOCK_FILE_URL, - example_images: MOCK_BUCKET_FILES, - task_bid_price: 0.5, - }, - advanced: {}, - }; - - const response = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(jobEntity!.manifestUrl).toBeDefined(); - expect(manifest.requester_restricted_answer_set).toMatchObject({ - Label: { en: 'Label' }, - }); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should create an hCaptcha job with immo type successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance + paidAmount); - - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucket_name: 'bucket', - path: '', - }, - accuracy_target: 0.9, - completion_date: new Date(), - min_requests: 1, - max_requests: 10, - annotations: { - type_of_job: JobCaptchaShapeType.IMMO, - label: 'Label', - labeling_prompt: MOCK_REQUESTER_DESCRIPTION, - ground_truths: MOCK_FILE_URL, - example_images: MOCK_BUCKET_FILES, - task_bid_price: 0.5, - }, - advanced: {}, - }; - - const response = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - expect(jobEntity!.manifestUrl).toBeDefined(); - - const manifest = (await storageService.downloadJsonLikeData( - jobEntity!.manifestUrl, - )) as any; - - expect(jobEntity!.manifestUrl).toBeDefined(); - expect(manifest.requester_restricted_answer_set).toMatchObject({ - Label: { en: 'Label' }, - }); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - paidAmount += paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should handle not enough funds error', async () => { - const hCaptchaDto = { - chain_id: ChainId.LOCALHOST, - data: { - provider: StorageProviders.LOCAL, - region: AWSRegions.EU_CENTRAL_1, - bucketName: 'bucket', - path: '', - }, - accuracyTarget: 0.9, - minRequests: 1, - maxRequests: 10, - annotations: { - typeOfJob: JobCaptchaShapeType.COMPARISON, - labelingPrompt: MOCK_REQUESTER_DESCRIPTION, - groundTruths: MOCK_FILE_URL, - exampleImages: MOCK_BUCKET_FILES, - taskBidPrice: 100, - }, - completionDate: new Date(), - advanced: {}, - }; - - const invalidQuickLaunchResponse = await request(app.getHttpServer()) - .post('/job/hCaptcha') - .set('Authorization', `Bearer ${accessToken}`) - .send(hCaptchaDto) - .expect(400); - - expect(invalidQuickLaunchResponse.status).toBe(HttpStatus.BAD_REQUEST); - expect(invalidQuickLaunchResponse.body.message).toBe( - ErrorJob.NotEnoughFunds, - ); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts deleted file mode 100644 index 5fce5d6f55..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts +++ /dev/null @@ -1,196 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { HttpStatus, INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserStatus } from '../../src/common/enums/user'; -import { UserService } from '../../src/modules/user/user.service'; -import { UserEntity } from '../../src/modules/user/user.entity'; -import setupE2eEnvironment from './env-setup'; -import { MOCK_FILE_HASH, MOCK_FILE_URL } from '../../test/constants'; -import { JobRequestType, JobStatus } from '../../src/common/enums/job'; -import { ChainId } from '@human-protocol/sdk'; -import { ErrorJob } from '../../src/common/constants/errors'; -import { - Currency, - PaymentSource, - PaymentStatus, - PaymentType, - TokenId, -} from '../../src/common/enums/payment'; -import { PaymentEntity } from '../../src/modules/payment/payment.entity'; -import { PaymentRepository } from '../../src/modules/payment/payment.repository'; -import { JobRepository } from '../../src/modules/job/job.repository'; -import { PaymentService } from '../../src/modules/payment/payment.service'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; -import { - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; - -describe('Quick launch E2E workflow', () => { - let app: INestApplication; - let paymentRepository: PaymentRepository; - let jobRepository: JobRepository; - let userService: UserService; - let paymentService: PaymentService; - - let userEntity: UserEntity; - let accessToken: string; - const initialBalance = 100; - - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - const paymentIntentId = crypto.randomBytes(16).toString('hex'); - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - paymentRepository = moduleFixture.get(PaymentRepository); - jobRepository = moduleFixture.get(JobRepository); - userService = moduleFixture.get(UserService); - paymentService = moduleFixture.get(PaymentService); - - userEntity = await userService.create({ - email, - password: 'Password1!', - hCaptchaToken: 'string', - }); - - userEntity.status = UserStatus.ACTIVE; - await userEntity.save(); - - const signInResponse = await request(app.getHttpServer()) - .post('/auth/signin') - .send({ - email, - password: 'Password1!', - h_captcha_token: 'string', - }); - - accessToken = signInResponse.body.access_token; - - const newPaymentEntity = new PaymentEntity(); - Object.assign(newPaymentEntity, { - userId: userEntity.id, - source: PaymentSource.FIAT, - type: PaymentType.DEPOSIT, - amount: initialBalance, - currency: Currency.USD, - rate: 1, - transaction: paymentIntentId, - status: PaymentStatus.SUCCEEDED, - }); - await paymentRepository.createUnique(newPaymentEntity); - }); - - afterAll(async () => { - await app.close(); - }); - - it('should create a job via quick launch successfully', async () => { - const balance_before = await paymentService.getUserBalance(userEntity.id); - expect(balance_before).toBe(initialBalance); - - const quickLaunchDto = { - chain_id: ChainId.LOCALHOST, - request_type: JobRequestType.HCAPTCHA, - manifest_url: MOCK_FILE_URL, - manifest_hash: MOCK_FILE_HASH, - fund_amount: 10, // HMT - }; - - const response = await request(app.getHttpServer()) - .post('/job/quick-launch') - .set('Authorization', `Bearer ${accessToken}`) - .send(quickLaunchDto) - .expect(201); - - expect(response.text).toBeDefined(); - expect(typeof response.text).toBe('string'); - - const jobEntity = await jobRepository.findOneByIdAndUserId( - Number(response.text), - userEntity.id, - ); - - expect(jobEntity).toBeDefined(); - expect(jobEntity!.status).toBe(JobStatus.PAID); - - const paymentEntities = await paymentRepository.findByUserAndStatus( - userEntity.id, - PaymentStatus.SUCCEEDED, - ); - - expect(paymentEntities[0]).toBeDefined(); - expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); - expect(paymentEntities[0].currency).toBe(TokenId.HMT); - - const paidAmount = paymentEntities[0].rate * paymentEntities[0].amount; - const balance_after = await paymentService.getUserBalance(userEntity.id); - expect(balance_after).toBe(initialBalance + paidAmount); - }); - - it('should handle not enough funds error', async () => { - const quickLaunchData = { - chain_id: ChainId.LOCALHOST, - request_type: JobRequestType.HCAPTCHA, - manifest_url: MOCK_FILE_URL, - manifest_hash: MOCK_FILE_HASH, - fund_amount: 100000000, // HMT - }; - - const invalidQuickLaunchResponse = await request(app.getHttpServer()) - .post('/job/quick-launch') - .set('Authorization', `Bearer ${accessToken}`) - .send(quickLaunchData) - .expect(400); - - expect(invalidQuickLaunchResponse.status).toBe(HttpStatus.BAD_REQUEST); - expect(invalidQuickLaunchResponse.body.message).toBe( - ErrorJob.NotEnoughFunds, - ); - }); - - it('should handle manifest hash does not exist error', async () => { - const quickLaunchData = { - chain_id: ChainId.LOCALHOST, - request_type: JobRequestType.HCAPTCHA, - manifest_url: 'http://example.com', - fund_amount: 10, // HMT - }; - - const invalidQuickLaunchResponse = await request(app.getHttpServer()) - .post('/job/quick-launch') - .set('Authorization', `Bearer ${accessToken}`) - .send(quickLaunchData) - .expect(409); - - expect(invalidQuickLaunchResponse.status).toBe(HttpStatus.CONFLICT); - expect(invalidQuickLaunchResponse.body.message).toBe( - ErrorJob.ManifestHashNotExist, - ); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts deleted file mode 100644 index 3a72c7068a..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts +++ /dev/null @@ -1,198 +0,0 @@ -import request from 'supertest'; -import * as crypto from 'crypto'; -import { HttpStatus, INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../src/app.module'; -import { UserRepository } from '../../src/modules/user/user.repository'; -import { TokenRepository } from '../../src/modules/auth/token.repository'; -import { TokenEntity, TokenType } from '../../src/modules/auth/token.entity'; -import { UserStatus } from '../../src/common/enums/user'; -import { UserService } from '../../src/modules/user/user.service'; -import { UserEntity } from '../../src/modules/user/user.entity'; -import { ErrorAuth } from '../../src/common/constants/errors'; -import setupE2eEnvironment from './env-setup'; -import { NetworkConfigService } from '../../src/common/config/network-config.service'; -import { Web3ConfigService } from '../../src/common/config/web3-config.service'; -import { ChainId } from '@human-protocol/sdk'; -import { - MOCK_PRIVATE_KEY, - MOCK_WEB3_NODE_HOST, - MOCK_WEB3_RPC_URL, -} from '../constants'; - -describe('Restore password E2E workflow', () => { - let app: INestApplication; - let userRepository: UserRepository; - let tokenRepository: TokenRepository; - let userService: UserService; - - let userEntity: UserEntity; - let tokenEntity: TokenEntity; - - const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; - - beforeAll(async () => { - setupE2eEnvironment(); - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }) - .overrideProvider(NetworkConfigService) - .useValue({ - networks: [ - { - chainId: ChainId.LOCALHOST, - rpcUrl: MOCK_WEB3_RPC_URL, - }, - ], - }) - .overrideProvider(Web3ConfigService) - .useValue({ - privateKey: MOCK_PRIVATE_KEY, - env: MOCK_WEB3_NODE_HOST, - }) - .compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - userRepository = moduleFixture.get(UserRepository); - tokenRepository = moduleFixture.get(TokenRepository); - userService = moduleFixture.get(UserService); - - userEntity = await userService.create({ - email, - password: 'Password1!', - hCaptchaToken: 'string', - }); - - userEntity.status = UserStatus.ACTIVE; - await userEntity.save(); - - tokenEntity = new TokenEntity(); - tokenEntity.type = TokenType.EMAIL; - tokenEntity.user = userEntity; - const date = new Date(); - tokenEntity.expiresAt = new Date(date.getTime() + 100000); - - await tokenRepository.createUnique(tokenEntity); - }); - - afterAll(async () => { - await app.close(); - }); - - it('should restore password successfully', async () => { - // Call forgot password endpoint - await request(app.getHttpServer()) - .post('/auth/forgot-password') - .send({ email }) - .expect(HttpStatus.NO_CONTENT); - - // Ensure old token was deleted and a new token was created - const oldTokenExists = await tokenRepository.findOneByUuidAndType( - tokenEntity.uuid, - TokenType.PASSWORD, - ); - expect(oldTokenExists).toBeNull(); - - const newToken = await tokenRepository.findOneByUserIdAndType( - userEntity.id, - TokenType.PASSWORD, - ); - expect(newToken).toBeDefined(); - - // Call restore password endpoint - const restorePasswordData = { - password: 'NewPassword1!', - token: newToken!.uuid, - hCaptchaToken: 'string', - }; - await request(app.getHttpServer()) - .post('/auth/restore-password') - .send(restorePasswordData) - .expect(HttpStatus.NO_CONTENT); - - // Ensure password was updated - const updatedUser = await userRepository.findById(userEntity!.id); - expect(updatedUser!.password).not.toEqual(userEntity.password); - - userEntity = updatedUser!; - - // Ensure new token was deleted - const deletedToken = await tokenRepository.findOneByUuidAndType( - newToken!.uuid, - TokenType.PASSWORD, - ); - expect(deletedToken).toBeNull(); - }); - - it('should handle invalid token error', async () => { - // Call forgot password endpoint - await request(app.getHttpServer()) - .post('/auth/forgot-password') - .send({ email }) - .expect(HttpStatus.NO_CONTENT); - - // Delete the new token - await tokenRepository.delete(tokenEntity.id); - - // Call restore password endpoint with invalid token - const invalidToken = '00000000-0000-0000-0000-000000000000'; - const restorePasswordData = { - password: 'NewPassword2!', - token: invalidToken, - hCaptchaToken: 'string', - }; - - const invalidRestorePasswordResponse = await request(app.getHttpServer()) - .post('/auth/restore-password') - .send(restorePasswordData) - .expect(HttpStatus.FORBIDDEN); - - expect(invalidRestorePasswordResponse.status).toBe(HttpStatus.FORBIDDEN); - expect(invalidRestorePasswordResponse.body.message).toBe( - ErrorAuth.InvalidToken, - ); - - // Ensure password was not updated - const updatedUser = await userRepository.findById(userEntity.id); - expect(updatedUser!.password).toEqual(userEntity.password); - }); - - it('should handle token expired error', async () => { - // Call forgot password endpoint - await request(app.getHttpServer()) - .post('/auth/forgot-password') - .send({ email }) - .expect(HttpStatus.NO_CONTENT); - - // Expire the new token - const date = new Date(); - const expiredToken = await tokenRepository.findOneByUserIdAndType( - userEntity.id, - TokenType.PASSWORD, - ); - expiredToken!.expiresAt = new Date(date.getTime() - 100000); // Set token expiration in the past - await expiredToken!.save(); - - // Call restore password endpoint with expired token - const expiredTokenValue = expiredToken!.uuid; - const restorePasswordData = { - password: 'NewPassword2!', - token: expiredTokenValue, - hCaptchaToken: 'string', - }; - const invalidRestorePasswordResponse = await request(app.getHttpServer()) - .post('/auth/restore-password') - .send(restorePasswordData); - - expect(invalidRestorePasswordResponse.status).toBe(HttpStatus.FORBIDDEN); - expect(invalidRestorePasswordResponse.body.message).toBe( - ErrorAuth.TokenExpired, - ); - - // Ensure password was not updated - const updatedUser = await userRepository.findById(userEntity.id); - expect(updatedUser!.password).toEqual(userEntity.password); - }); -}); diff --git a/packages/apps/job-launcher/server/test/e2e/utils.ts b/packages/apps/job-launcher/server/test/e2e/utils.ts deleted file mode 100644 index 726393296c..0000000000 --- a/packages/apps/job-launcher/server/test/e2e/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -export async function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export function getFileNameFromURL(url: string): string { - const parts = url.split('/'); - return parts[parts.length - 1]; -} diff --git a/packages/apps/job-launcher/server/test/jest-e2e.json b/packages/apps/job-launcher/server/test/jest-e2e.json deleted file mode 100644 index e9d912f3e3..0000000000 --- a/packages/apps/job-launcher/server/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} From d8d91982be92a8bde3364e149da0cf29494cf753 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 09:10:08 +0100 Subject: [PATCH 03/27] chore(deps-dev): bump vite from 5.4.14 to 6.2.0 (#3124) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.14 to 6.2.0. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/create-vite@6.2.0/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/dashboard/client/package.json | 2 +- packages/apps/faucet/client/package.json | 2 +- .../exchange-oracle/client/package.json | 2 +- packages/apps/human-app/frontend/package.json | 2 +- .../apps/job-launcher/client/package.json | 2 +- packages/apps/staking/package.json | 2 +- yarn.lock | 411 ++++++++++++------ 7 files changed, 295 insertions(+), 128 deletions(-) diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json index 9ff13324a6..97ff99ed68 100644 --- a/packages/apps/dashboard/client/package.json +++ b/packages/apps/dashboard/client/package.json @@ -54,7 +54,7 @@ "sass": "^1.85.0", "stylelint-prettier": "^5.0.0", "typescript": "^5.6.3", - "vite": "^5.4.7", + "vite": "^6.2.0", "vite-plugin-svgr": "^4.2.0" } } diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json index 56a538be25..d1b06fe23e 100644 --- a/packages/apps/faucet/client/package.json +++ b/packages/apps/faucet/client/package.json @@ -22,7 +22,7 @@ "eslint-plugin-import": "^2.29.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^5.1.0", - "vite": "^5.4.7", + "vite": "^6.2.0", "vite-plugin-node-polyfills": "^0.22.0", "vitest": "^1.6.0" }, diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json index 23ebd79cfe..64ab096567 100644 --- a/packages/apps/fortune/exchange-oracle/client/package.json +++ b/packages/apps/fortune/exchange-oracle/client/package.json @@ -51,7 +51,7 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.11", "typescript": "^5.6.3", - "vite": "^5.4.7" + "vite": "^6.2.0" }, "lint-staged": { "*.{ts,tsx}": [ diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index f132098727..81b3e2f3c0 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -74,7 +74,7 @@ "lint-staged": "^15.4.3", "prettier": "^3.4.2", "typescript": "^5.6.3", - "vite": "^5.4.7", + "vite": "^6.2.0", "vitest": "^1.2.2" } } diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 099ef4b1d6..352922b3c3 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -80,7 +80,7 @@ "identity-obj-proxy": "^3.0.0", "jsdom": "^25.0.1", "resize-observer-polyfill": "^1.5.1", - "vite": "^5.4.7", + "vite": "^6.2.0", "vite-plugin-node-polyfills": "^0.22.0", "vitest": "^1.6.0" }, diff --git a/packages/apps/staking/package.json b/packages/apps/staking/package.json index b6ac6e8c25..c6f6e4b925 100644 --- a/packages/apps/staking/package.json +++ b/packages/apps/staking/package.json @@ -54,7 +54,7 @@ "eslint-plugin-react-refresh": "^0.4.11", "sass": "^1.85.0", "typescript": "^5.6.3", - "vite": "^5.4.7" + "vite": "^6.2.0" }, "lint-staged": { "*.{ts,tsx}": [ diff --git a/yarn.lock b/yarn.lock index 5e348e1f55..58f8bd750a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1837,116 +1837,241 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== +"@esbuild/aix-ppc64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64" + integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ== + "@esbuild/android-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== +"@esbuild/android-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f" + integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g== + "@esbuild/android-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== +"@esbuild/android-arm@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b" + integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g== + "@esbuild/android-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== +"@esbuild/android-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163" + integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg== + "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== +"@esbuild/darwin-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz#fa394164b0d89d4fdc3a8a21989af70ef579fa2c" + integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw== + "@esbuild/darwin-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== +"@esbuild/darwin-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a" + integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg== + "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== +"@esbuild/freebsd-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce" + integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w== + "@esbuild/freebsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== +"@esbuild/freebsd-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7" + integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A== + "@esbuild/linux-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== +"@esbuild/linux-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73" + integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg== + "@esbuild/linux-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== +"@esbuild/linux-arm@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3" + integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg== + "@esbuild/linux-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== +"@esbuild/linux-ia32@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19" + integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg== + "@esbuild/linux-loong64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== +"@esbuild/linux-loong64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7" + integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw== + "@esbuild/linux-mips64el@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== +"@esbuild/linux-mips64el@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1" + integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ== + "@esbuild/linux-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== +"@esbuild/linux-ppc64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951" + integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw== + "@esbuild/linux-riscv64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== +"@esbuild/linux-riscv64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987" + integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA== + "@esbuild/linux-s390x@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== +"@esbuild/linux-s390x@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4" + integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA== + "@esbuild/linux-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== +"@esbuild/linux-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a" + integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw== + +"@esbuild/netbsd-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b" + integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw== + "@esbuild/netbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== +"@esbuild/netbsd-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b" + integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA== + +"@esbuild/openbsd-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7" + integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw== + "@esbuild/openbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== +"@esbuild/openbsd-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde" + integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg== + "@esbuild/sunos-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== +"@esbuild/sunos-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92" + integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg== + "@esbuild/win32-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== +"@esbuild/win32-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c" + integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw== + "@esbuild/win32-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== +"@esbuild/win32-ia32@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079" + integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA== + "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== +"@esbuild/win32-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b" + integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" @@ -4874,100 +4999,100 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz#9b726b4dcafb9332991e9ca49d54bafc71d9d87f" - integrity sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg== - -"@rollup/rollup-android-arm64@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz#88326ff46168a47851077ca0bf0c442689ec088f" - integrity sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA== - -"@rollup/rollup-darwin-arm64@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz#b8fbcc9389bc6fad3334a1d16dbeaaa5637c5772" - integrity sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg== - -"@rollup/rollup-darwin-x64@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz#1aa2bcad84c0fb5902e945d88822e17a4f661d51" - integrity sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg== - -"@rollup/rollup-freebsd-arm64@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz#29c54617e0929264dcb6416597d6d7481696e49f" - integrity sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ== - -"@rollup/rollup-freebsd-x64@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz#a8b58ab7d31882559d93f2d1b5863d9e4b4b2678" - integrity sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ== - -"@rollup/rollup-linux-arm-gnueabihf@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz#a844e1978c8b9766b169ecb1cb5cc0d8a3f05930" - integrity sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg== - -"@rollup/rollup-linux-arm-musleabihf@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz#6b44c3b7257985d71b087fcb4ef01325e2fff201" - integrity sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg== - -"@rollup/rollup-linux-arm64-gnu@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz#ebb499cf1720115256d0c9ae7598c90cc2251bc5" - integrity sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA== - -"@rollup/rollup-linux-arm64-musl@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz#9658221b59d9e5643348f9a52fa5ef35b4dc07b1" - integrity sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q== - -"@rollup/rollup-linux-loongarch64-gnu@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz#19418cc57579a5655af2d850a89d74b3f7e9aa92" - integrity sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz#fe0bce7778cb6ce86898c781f3f11369d1a4952c" - integrity sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ== - -"@rollup/rollup-linux-riscv64-gnu@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz#9c158360abf6e6f7794285642ba0898c580291f6" - integrity sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg== - -"@rollup/rollup-linux-s390x-gnu@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz#f9113498d22962baacdda008b5587d568b05aa34" - integrity sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw== - -"@rollup/rollup-linux-x64-gnu@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz#aec8d4cdf911cd869a72b8bd00833cb426664e0c" - integrity sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw== - -"@rollup/rollup-linux-x64-musl@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz#61c0a146bdd1b5e0dcda33690dd909b321d8f20f" - integrity sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A== - -"@rollup/rollup-win32-arm64-msvc@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz#c6c5bf290a3a459c18871110bc2e7009ce35b15a" - integrity sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA== - -"@rollup/rollup-win32-ia32-msvc@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz#16ca6bdadc9e054818b9c51f8dac82f6b8afab81" - integrity sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA== - -"@rollup/rollup-win32-x64-msvc@4.34.6": - version "4.34.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz#f3d03ce2d82723eb089188ea1494a719b09e1561" - integrity sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w== +"@rollup/rollup-android-arm-eabi@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz#731df27dfdb77189547bcef96ada7bf166bbb2fb" + integrity sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw== + +"@rollup/rollup-android-arm64@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz#4bea6db78e1f6927405df7fe0faf2f5095e01343" + integrity sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q== + +"@rollup/rollup-darwin-arm64@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz#a7aab77d44be3c44a20f946e10160f84e5450e7f" + integrity sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q== + +"@rollup/rollup-darwin-x64@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz#c572c024b57ee8ddd1b0851703ace9eb6cc0dd82" + integrity sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw== + +"@rollup/rollup-freebsd-arm64@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz#cf74f8113b5a83098a5c026c165742277cbfb88b" + integrity sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA== + +"@rollup/rollup-freebsd-x64@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz#39561f3a2f201a4ad6a01425b1ff5928154ecd7c" + integrity sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q== + +"@rollup/rollup-linux-arm-gnueabihf@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz#980d6061e373bfdaeb67925c46d2f8f9b3de537f" + integrity sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g== + +"@rollup/rollup-linux-arm-musleabihf@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz#f91a90f30dc00d5a64ac2d9bbedc829cd3cfaa78" + integrity sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA== + +"@rollup/rollup-linux-arm64-gnu@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz#fac700fa5c38bc13a0d5d34463133093da4c92a0" + integrity sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A== + +"@rollup/rollup-linux-arm64-musl@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz#f50ecccf8c78841ff6df1706bc4782d7f62bf9c3" + integrity sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q== + +"@rollup/rollup-linux-loongarch64-gnu@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz#5869dc0b28242da6553e2b52af41374f4038cd6e" + integrity sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz#5cdd9f851ce1bea33d6844a69f9574de335f20b1" + integrity sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw== + +"@rollup/rollup-linux-riscv64-gnu@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz#ef5dc37f4388f5253f0def43e1440ec012af204d" + integrity sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw== + +"@rollup/rollup-linux-s390x-gnu@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz#7dbc3ccbcbcfb3e65be74538dfb6e8dd16178fde" + integrity sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA== + +"@rollup/rollup-linux-x64-gnu@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz#5783fc0adcab7dc069692056e8ca8d83709855ce" + integrity sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA== + +"@rollup/rollup-linux-x64-musl@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz#00b6c29b298197a384e3c659910b47943003a678" + integrity sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ== + +"@rollup/rollup-win32-arm64-msvc@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz#cbfee01f1fe73791c35191a05397838520ca3cdd" + integrity sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ== + +"@rollup/rollup-win32-ia32-msvc@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz#95cdbdff48fe6c948abcf6a1d500b2bd5ce33f62" + integrity sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w== + +"@rollup/rollup-win32-x64-msvc@4.34.8": + version "4.34.8" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz#4cdb2cfae69cdb7b1a3cc58778e820408075e928" + integrity sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g== "@rtsao/scc@^1.1.0": version "1.1.0" @@ -10826,6 +10951,37 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" +esbuild@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92" + integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.0" + "@esbuild/android-arm" "0.25.0" + "@esbuild/android-arm64" "0.25.0" + "@esbuild/android-x64" "0.25.0" + "@esbuild/darwin-arm64" "0.25.0" + "@esbuild/darwin-x64" "0.25.0" + "@esbuild/freebsd-arm64" "0.25.0" + "@esbuild/freebsd-x64" "0.25.0" + "@esbuild/linux-arm" "0.25.0" + "@esbuild/linux-arm64" "0.25.0" + "@esbuild/linux-ia32" "0.25.0" + "@esbuild/linux-loong64" "0.25.0" + "@esbuild/linux-mips64el" "0.25.0" + "@esbuild/linux-ppc64" "0.25.0" + "@esbuild/linux-riscv64" "0.25.0" + "@esbuild/linux-s390x" "0.25.0" + "@esbuild/linux-x64" "0.25.0" + "@esbuild/netbsd-arm64" "0.25.0" + "@esbuild/netbsd-x64" "0.25.0" + "@esbuild/openbsd-arm64" "0.25.0" + "@esbuild/openbsd-x64" "0.25.0" + "@esbuild/sunos-x64" "0.25.0" + "@esbuild/win32-arm64" "0.25.0" + "@esbuild/win32-ia32" "0.25.0" + "@esbuild/win32-x64" "0.25.0" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -16615,10 +16771,10 @@ postcss@8.4.49: picocolors "^1.1.1" source-map-js "^1.2.1" -postcss@^8.4.43: - version "8.5.2" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.2.tgz#e7b99cb9d2ec3e8dd424002e7c16517cb2b846bd" - integrity sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA== +postcss@^8.4.43, postcss@^8.5.3: + version "8.5.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== dependencies: nanoid "^3.3.8" picocolors "^1.1.1" @@ -17628,32 +17784,32 @@ rlp@^2.2.3, rlp@^2.2.4: dependencies: bn.js "^5.2.0" -rollup@^4.20.0: - version "4.34.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.6.tgz#a07e4d2621759e29034d909655e7a32eee9195c9" - integrity sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ== +rollup@^4.20.0, rollup@^4.30.1: + version "4.34.8" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.8.tgz#e859c1a51d899aba9bcf451d4eed1d11fb8e2a6e" + integrity sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.34.6" - "@rollup/rollup-android-arm64" "4.34.6" - "@rollup/rollup-darwin-arm64" "4.34.6" - "@rollup/rollup-darwin-x64" "4.34.6" - "@rollup/rollup-freebsd-arm64" "4.34.6" - "@rollup/rollup-freebsd-x64" "4.34.6" - "@rollup/rollup-linux-arm-gnueabihf" "4.34.6" - "@rollup/rollup-linux-arm-musleabihf" "4.34.6" - "@rollup/rollup-linux-arm64-gnu" "4.34.6" - "@rollup/rollup-linux-arm64-musl" "4.34.6" - "@rollup/rollup-linux-loongarch64-gnu" "4.34.6" - "@rollup/rollup-linux-powerpc64le-gnu" "4.34.6" - "@rollup/rollup-linux-riscv64-gnu" "4.34.6" - "@rollup/rollup-linux-s390x-gnu" "4.34.6" - "@rollup/rollup-linux-x64-gnu" "4.34.6" - "@rollup/rollup-linux-x64-musl" "4.34.6" - "@rollup/rollup-win32-arm64-msvc" "4.34.6" - "@rollup/rollup-win32-ia32-msvc" "4.34.6" - "@rollup/rollup-win32-x64-msvc" "4.34.6" + "@rollup/rollup-android-arm-eabi" "4.34.8" + "@rollup/rollup-android-arm64" "4.34.8" + "@rollup/rollup-darwin-arm64" "4.34.8" + "@rollup/rollup-darwin-x64" "4.34.8" + "@rollup/rollup-freebsd-arm64" "4.34.8" + "@rollup/rollup-freebsd-x64" "4.34.8" + "@rollup/rollup-linux-arm-gnueabihf" "4.34.8" + "@rollup/rollup-linux-arm-musleabihf" "4.34.8" + "@rollup/rollup-linux-arm64-gnu" "4.34.8" + "@rollup/rollup-linux-arm64-musl" "4.34.8" + "@rollup/rollup-linux-loongarch64-gnu" "4.34.8" + "@rollup/rollup-linux-powerpc64le-gnu" "4.34.8" + "@rollup/rollup-linux-riscv64-gnu" "4.34.8" + "@rollup/rollup-linux-s390x-gnu" "4.34.8" + "@rollup/rollup-linux-x64-gnu" "4.34.8" + "@rollup/rollup-linux-x64-musl" "4.34.8" + "@rollup/rollup-win32-arm64-msvc" "4.34.8" + "@rollup/rollup-win32-ia32-msvc" "4.34.8" + "@rollup/rollup-win32-x64-msvc" "4.34.8" fsevents "~2.3.2" rrweb-cssom@^0.7.1: @@ -19914,7 +20070,7 @@ vite-plugin-svgr@^4.2.0: "@svgr/core" "^8.1.0" "@svgr/plugin-jsx" "^8.1.0" -vite@^5.0.0, vite@^5.4.7: +vite@^5.0.0: version "5.4.14" resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408" integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA== @@ -19925,6 +20081,17 @@ vite@^5.0.0, vite@^5.4.7: optionalDependencies: fsevents "~2.3.3" +vite@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.0.tgz#9dcb543380dab18d8384eb840a76bf30d78633f0" + integrity sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ== + dependencies: + esbuild "^0.25.0" + postcss "^8.5.3" + rollup "^4.30.1" + optionalDependencies: + fsevents "~2.3.3" + vitest@^1.2.2, vitest@^1.6.0: version "1.6.1" resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.1.tgz#b4a3097adf8f79ac18bc2e2e0024c534a7a78d2f" From 589343dee4f379e24e871551a2c5c0430765b772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:18:42 +0100 Subject: [PATCH 04/27] [Human app] Governance banner (#3107) * feat(governance-banner): add GovernanceBanner component with active proposal display * feat(governance): add environment variables for governor address and governance URL * fix(governance-banner): remove outdated comment and update network to Sepolia for active proposal query * Update use-active-proposal-query.ts * fix(governance-banner): adjust margin and update block number fetching logic --- packages/apps/human-app/frontend/.env.example | 2 + .../components/governance-banner.tsx | 134 + .../hooks/use-active-proposal-query.ts | 74 + .../abi/MetaHumanGovernor.json | 2236 +++++++++++++++++ .../components/layout/protected/layout.tsx | 2 + .../apps/human-app/frontend/src/shared/env.ts | 2 + .../src/shared/styles/color-palette.ts | 9 +- .../src/shared/styles/dark-color-palette.ts | 8 + 8 files changed, 2465 insertions(+), 2 deletions(-) create mode 100644 packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts create mode 100644 packages/apps/human-app/frontend/src/modules/smart-contracts/abi/MetaHumanGovernor.json diff --git a/packages/apps/human-app/frontend/.env.example b/packages/apps/human-app/frontend/.env.example index 2035eeae60..fbd946ad50 100644 --- a/packages/apps/human-app/frontend/.env.example +++ b/packages/apps/human-app/frontend/.env.example @@ -48,6 +48,8 @@ VITE_H_CAPTCHA_ORACLE_TASK_TYPES= #string => lists of job types eg.: Image label # network if network is equl to 'testnet' app will use first tesntet chain from .src/smart-contracts/chains.ts # and first mainnet chain for 'mainnet' VITE_NETWORK= # mainnet|testnet +VITE_GOVERNOR_ADDRESS= +VITE_GOVERNANCE_URL= ## Web3 setup # set SC addresses according to https://human-protocol.gitbook.io/hub/human-tech-docs/architecture/components/smart-contracts/contract-addresses diff --git a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx new file mode 100644 index 0000000000..4c86c51601 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx @@ -0,0 +1,134 @@ +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import { Grid, Link as MuiLink, Typography } from '@mui/material'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { env } from '@/shared/env'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { useActiveProposalQuery } from '../hooks/use-active-proposal-query'; + +export function GovernanceBanner() { + const { t } = useTranslation(); + const { data, isLoading, isError } = useActiveProposalQuery(); + const { colorPalette } = useColorMode(); + const [timeRemaining, setTimeRemaining] = useState('00:00:00'); + const isMobile = useIsMobile('lg'); + + useEffect(() => { + if (!data?.deadline) return; + + const timer = setInterval(() => { + const now = Math.floor(Date.now() / 1000); + const diff = data.deadline - now; + + if (diff <= 0) { + setTimeRemaining('00:00:00'); + } else { + const hours = Math.floor(diff / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = diff % 60; + + const hh = hours.toString().padStart(2, '0'); + const mm = minutes.toString().padStart(2, '0'); + const ss = seconds.toString().padStart(2, '0'); + + setTimeRemaining(`${hh}:${mm}:${ss}`); + } + }, 1000); + + return () => { + clearInterval(timer); + }; + }, [data?.deadline]); + + if (isLoading || isError || !data) { + return null; + } + + const forVotes = parseFloat(data.forVotes) || 0; + const againstVotes = parseFloat(data.againstVotes) || 0; + const abstainVotes = parseFloat(data.abstainVotes) || 0; + const totalVotes = forVotes + againstVotes + abstainVotes; + + return ( + + {/* Left side: Countdown & "X votes" */} + + + + {t('governance.timeToReveal', 'Time to reveal vote')}: + + + + {timeRemaining} + + + {totalVotes} {t('governance.votes', 'votes')} + + + + {/* Right side: "More details" link */} + + + {t('governance.moreDetails', 'More details')} → + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts b/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts new file mode 100644 index 0000000000..2ce4913f56 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts @@ -0,0 +1,74 @@ +import { useQuery } from '@tanstack/react-query'; +import { ethers } from 'ethers'; +import * as wagmiChains from 'wagmi/chains'; +import GovernorABI from '@/modules/smart-contracts/abi/MetaHumanGovernor.json'; +import { env } from '@/shared/env'; + +enum ProposalState { + PENDING, + ACTIVE, + CANCELED, + DEFEATED, + SUCCEEDED, + QUEUED, + EXPIRED, + EXECUTED, +} + +async function fetchActiveProposalFn() { + const provider = new ethers.JsonRpcProvider( + env.VITE_NETWORK === 'mainnet' + ? wagmiChains.polygon.rpcUrls.default.http[0] + : wagmiChains.sepolia.rpcUrls.default.http[0] + ); + const contract = new ethers.Contract( + env.VITE_GOVERNOR_ADDRESS, + GovernorABI, + provider + ); + const filter = contract.filters.ProposalCreated(); + const logs = await contract.queryFilter( + filter, + env.VITE_NETWORK === 'mainnet' + ? 68058296 + : (await provider.getBlockNumber()) - 10000, + 'latest' + ); + + for (const log of logs) { + const parsed = contract.interface.parseLog(log); + const proposalId = parsed?.args.proposalId as ethers.BigNumberish; + const state = Number(await contract.state(proposalId)) as ProposalState; + + if (state === ProposalState.ACTIVE) { + const votesResult = (await contract.proposalVotes(proposalId)) as [ + ethers.BigNumberish, + ethers.BigNumberish, + ethers.BigNumberish, + ]; + const [againstBn, forBn, abstainBn] = votesResult; + const forVotes = ethers.formatEther(forBn); + const againstVotes = ethers.formatEther(againstBn); + const abstainVotes = ethers.formatEther(abstainBn); + + const deadline = Number(await contract.proposalDeadline(proposalId)); + + return { + proposalId: proposalId.toString(), + forVotes, + againstVotes, + abstainVotes, + deadline, + }; + } + } + + return null; +} + +export function useActiveProposalQuery() { + return useQuery({ + queryKey: ['governanceActiveProposal'], + queryFn: fetchActiveProposalFn, + }); +} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/abi/MetaHumanGovernor.json b/packages/apps/human-app/frontend/src/modules/smart-contracts/abi/MetaHumanGovernor.json new file mode 100644 index 0000000000..f4cbe8d60f --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/smart-contracts/abi/MetaHumanGovernor.json @@ -0,0 +1,2236 @@ +[ + { + "inputs": [ + { + "internalType": "contract IVotes", + "name": "_token", + "type": "address" + }, + { + "internalType": "contract TimelockController", + "name": "_timelock", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "contractAddress", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "chainId", + "type": "uint16" + } + ], + "internalType": "struct CrossChainGovernorCountingSimple.CrossChainAddress[]", + "name": "_spokeContracts", + "type": "tuple[]" + }, + { + "internalType": "uint16", + "name": "_chainId", + "type": "uint16" + }, + { + "internalType": "address", + "name": "_wormholeRelayerAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_magistrateAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_secondsPerBlock", + "type": "uint256" + }, + { + "internalType": "uint48", + "name": "_votingDelayInSeconds", + "type": "uint48" + }, + { + "internalType": "uint32", + "name": "_votingPeriodInSeconds", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "_proposalThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_quorumFraction", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "CheckpointUnorderedInsertion", + "type": "error" + }, + { + "inputs": [], + "name": "CollectionPhaseAlreadyStarted", + "type": "error" + }, + { + "inputs": [], + "name": "CollectionPhaseUnfinished", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "GovernorAlreadyCastVote", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "GovernorAlreadyQueuedProposal", + "type": "error" + }, + { + "inputs": [], + "name": "GovernorDisabledDeposit", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "votes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + } + ], + "name": "GovernorInsufficientProposerVotes", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "targets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "calldatas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "values", + "type": "uint256" + } + ], + "name": "GovernorInvalidProposalLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "quorumNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "quorumDenominator", + "type": "uint256" + } + ], + "name": "GovernorInvalidQuorumFraction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "GovernorInvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "GovernorInvalidVoteType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "votingPeriod", + "type": "uint256" + } + ], + "name": "GovernorInvalidVotingPeriod", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "GovernorNonexistentProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "GovernorNotQueuedProposal", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "GovernorOnlyExecutor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "GovernorOnlyProposer", + "type": "error" + }, + { + "inputs": [], + "name": "GovernorQueueNotImplemented", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + } + ], + "name": "GovernorRestrictedProposer", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "enum IGovernor.ProposalState", + "name": "current", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "expectedStates", + "type": "bytes32" + } + ], + "name": "GovernorUnexpectedProposalState", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntendedRecipient", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [], + "name": "MessageAlreadyProcessed", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyMessagesFromSpokeReceived", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyRelayerAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ProposalAlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "QueueEmpty", + "type": "error" + }, + { + "inputs": [], + "name": "QueueFull", + "type": "error" + }, + { + "inputs": [], + "name": "RequestAfterVotePeriodOver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousMagistrate", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newMagistrate", + "type": "address" + } + ], + "name": "MagistrateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "signatures", + "type": "string[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "voteStart", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "voteEnd", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "name": "ProposalCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "etaSeconds", + "type": "uint256" + } + ], + "name": "ProposalQueued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldProposalThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newProposalThreshold", + "type": "uint256" + } + ], + "name": "ProposalThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldQuorumNumerator", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newQuorumNumerator", + "type": "uint256" + } + ], + "name": "QuorumNumeratorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "contractAddress", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "chainId", + "type": "uint16" + } + ], + "indexed": true, + "internalType": "struct CrossChainGovernorCountingSimple.CrossChainAddress[]", + "name": "spokes", + "type": "tuple[]" + } + ], + "name": "SpokesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTimelock", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTimelock", + "type": "address" + } + ], + "name": "TimelockChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "VoteCast", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "params", + "type": "bytes" + } + ], + "name": "VoteCastWithParams", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldVotingDelay", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVotingDelay", + "type": "uint256" + } + ], + "name": "VotingDelaySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldVotingPeriod", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVotingPeriod", + "type": "uint256" + } + ], + "name": "VotingPeriodSet", + "type": "event" + }, + { + "inputs": [], + "name": "BALLOT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CLOCK_MODE", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "COUNTING_MODE", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "EXTENDED_BALLOT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "cancel", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + } + ], + "name": "castVote", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "castVoteBySig", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "castVoteWithReason", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "string", + "name": "reason", + "type": "string" + }, + { + "internalType": "bytes", + "name": "params", + "type": "bytes" + } + ], + "name": "castVoteWithReasonAndParams", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "string", + "name": "reason", + "type": "string" + }, + { + "internalType": "bytes", + "name": "params", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "castVoteWithReasonAndParamsBySig", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "chainId", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "clock", + "outputs": [ + { + "internalType": "uint48", + "name": "", + "type": "uint48" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "collectionFinished", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "collectionStarted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "name": "crossChainPropose", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "execute", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "timepoint", + "type": "uint256" + } + ], + "name": "getVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "timepoint", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "params", + "type": "bytes" + } + ], + "name": "getVotesWithParams", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasVoted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "hashProposal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "magistrate", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "processedMessages", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalDeadline", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalEta", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalNeedsQueuing", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalProposer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposalThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "againstVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "abstainVotes", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "", + "type": "bytes[]" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "propose", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "queue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "quorum", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumDenominator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "timepoint", + "type": "uint256" + } + ], + "name": "quorumNumerator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumNumerator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "internalType": "bytes[]", + "name": "additionalVaas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "sourceAddress", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "sourceChain", + "type": "uint16" + }, + { + "internalType": "bytes32", + "name": "deliveryHash", + "type": "bytes32" + } + ], + "name": "receiveWormholeMessages", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "relay", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "requestCollections", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "secondsPerBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newProposalThreshold", + "type": "uint256" + } + ], + "name": "setProposalThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint48", + "name": "newVotingDelay", + "type": "uint48" + } + ], + "name": "setVotingDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "newVotingPeriod", + "type": "uint32" + } + ], + "name": "setVotingPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "spokeContracts", + "outputs": [ + { + "internalType": "bytes32", + "name": "contractAddress", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "chainId", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "name": "spokeContractsMapping", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "name": "spokeContractsMappingSnapshots", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "spokeContractsSnapshots", + "outputs": [ + { + "internalType": "bytes32", + "name": "contractAddress", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "chainId", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "name": "spokeVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "forVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "againstVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "abstainVotes", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "state", + "outputs": [ + { + "internalType": "enum IGovernor.ProposalState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "timelock", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract IERC5805", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newMagistrate", + "type": "address" + } + ], + "name": "transferMagistrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newQuorumNumerator", + "type": "uint256" + } + ], + "name": "updateQuorumNumerator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "contractAddress", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "chainId", + "type": "uint16" + } + ], + "internalType": "struct CrossChainGovernorCountingSimple.CrossChainAddress[]", + "name": "_spokeContracts", + "type": "tuple[]" + } + ], + "name": "updateSpokeContracts", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TimelockController", + "name": "newTimelock", + "type": "address" + } + ], + "name": "updateTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "votingDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "votingPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "wormholeRelayer", + "outputs": [ + { + "internalType": "contract IWormholeRelayer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx b/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx index ae589f1e45..85b6c8cf20 100644 --- a/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx @@ -8,6 +8,7 @@ import type { PageHeaderProps } from '@/shared/components/layout/protected/page- import { PageHeader } from '@/shared/components/layout/protected/page-header'; import { breakpoints } from '@/shared/styles/breakpoints'; import { useIsHCaptchaLabelingPage } from '@/shared/hooks/use-is-hcaptcha-labeling-page'; +import { GovernanceBanner } from '@/modules/governance-banner/components/governance-banner'; import { Footer } from '../footer'; import { Navbar } from './navbar'; @@ -117,6 +118,7 @@ export function Layout({ }, }} > + diff --git a/packages/apps/human-app/frontend/src/shared/env.ts b/packages/apps/human-app/frontend/src/shared/env.ts index a65c1bb2f5..6875df8211 100644 --- a/packages/apps/human-app/frontend/src/shared/env.ts +++ b/packages/apps/human-app/frontend/src/shared/env.ts @@ -30,6 +30,8 @@ const envSchema = z.object({ return iconsArray; }), VITE_NETWORK: z.enum(['mainnet', 'testnet']), + VITE_GOVERNOR_ADDRESS: z.string(), + VITE_GOVERNANCE_URL: z.string(), VITE_H_CAPTCHA_ORACLE_ANNOTATION_TOOL: z.string(), VITE_H_CAPTCHA_ORACLE_ROLE: z.string(), VITE_H_CAPTCHA_ORACLE_ADDRESS: z.string(), diff --git a/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts b/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts index bec54a7236..350f9a6933 100644 --- a/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts +++ b/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts @@ -45,8 +45,13 @@ export const colorPalette = { button: { disabled: '#E6E7EF', }, - // for 'warning', 'info' native colors from MUI were pointed as expected - // 'info' native colors from MUI were pointed as expected + banner: { + background: { primary: '#320A8D', secondary: '#1C133F' }, + text: { + primary: '#CDC7FF', + secondary: '#FFFFFF', + }, + }, }; export type ColorPalette = typeof colorPalette; diff --git a/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts b/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts index 5c41f9554f..566c333936 100644 --- a/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts +++ b/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts @@ -47,6 +47,14 @@ export const darkColorPalette = { button: { disabled: '#FFFFFF1F', }, + banner: { + background: { primary: '#320A8D', secondary: '#1C133F' }, + text: { + primary: '#FFFFFF', + secondary: '#CDC7FF', + }, + }, + // for 'warning', 'info' native colors from MUI were pointed as expected // 'info' native colors from MUI were pointed as expected } satisfies typeof colorPalette; From 6879d307fed1e1752dbdf9b684ff75f0cda37475 Mon Sep 17 00:00:00 2001 From: mpblocky <185767042+mpblocky@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:04:22 +0100 Subject: [PATCH 05/27] [HUMAN App] chore: cleanup hcaptcha module (#3127) --- .../frontend/src/modules/signin/worker/sign-in-form.tsx | 2 +- .../src/modules/signup/worker/views/sign-up-worker.page.tsx | 2 +- .../modules/worker/oracle-registration/registration-form.tsx | 2 +- .../worker/views/email-verification/verify-email.page.tsx | 2 +- .../worker/views/reset-password/reset-password.page.tsx | 2 +- .../views/send-reset-link/send-reset-link-success.page.tsx | 2 +- .../worker/views/send-reset-link/send-reset-link.page.tsx | 2 +- .../src/shared/components/hcaptcha/h-captcha-form.tsx | 3 +-- .../frontend/src/shared/components/hcaptcha/h-captcha.tsx | 5 ++++- .../frontend/src/shared/components/hcaptcha/index.ts | 1 + .../frontend/src/shared/components/hcaptcha/types.ts | 3 --- 11 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 packages/apps/human-app/frontend/src/shared/components/hcaptcha/index.ts delete mode 100644 packages/apps/human-app/frontend/src/shared/components/hcaptcha/types.ts diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx b/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx index f6336f2f1a..ca6e34ecf8 100644 --- a/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx @@ -11,7 +11,7 @@ import { routerPaths } from '@/router/router-paths'; import { type SignInDto } from '@/modules/worker/services/sign-in/types'; import { signInDtoSchema } from '@/modules/worker/services/sign-in/schema'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; interface SignInFormProps { onSubmit: (data: SignInDto) => void; diff --git a/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx index f9a9eb4c08..a52a2409e9 100644 --- a/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx @@ -13,7 +13,7 @@ import { PageCard } from '@/shared/components/ui/page-card'; import { env } from '@/shared/env'; import { getErrorMessageForError } from '@/shared/errors'; import { Alert } from '@/shared/components/ui/alert'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { FetchError } from '@/api/fetcher'; import { useSignUpWorker } from '@/modules/signup/worker/hooks/use-sign-up-worker'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx index d5f8a7b418..97685e3d18 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx @@ -3,7 +3,7 @@ import { FormProvider, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { zodResolver } from '@hookform/resolvers/zod'; import { Button } from '@/shared/components/ui/button'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { registrationInExchangeOracleDtoSchema, type RegistrationInExchangeOracleDto, diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx index 073a8a2bd2..96167050dc 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx @@ -16,7 +16,7 @@ import { } from '@/modules/worker/services/resend-email-verification'; import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { Button } from '@/shared/components/ui/button'; import { useAuth } from '@/modules/auth/hooks/use-auth'; import { routerPaths } from '@/router/router-paths'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx index f42524c4a7..380848a8dd 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx @@ -18,7 +18,7 @@ import { import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { routerPaths } from '@/router/router-paths'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; export function ResetPasswordWorkerPage() { diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link-success.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link-success.page.tsx index 572cf86346..54faa1ebe1 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link-success.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link-success.page.tsx @@ -15,7 +15,7 @@ import { } from '@/modules/worker/services/send-reset-link'; import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { MailTo } from '@/shared/components/ui/mail-to'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { useColorMode } from '@/shared/contexts/color-mode'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link.page.tsx index 6e5b5ef0fd..5759d3657f 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link.page.tsx @@ -14,7 +14,7 @@ import { import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { useAuth } from '@/modules/auth/hooks/use-auth'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { routerPaths } from '@/router/router-paths'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; diff --git a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha-form.tsx b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha-form.tsx index d185792184..1f89dba444 100644 --- a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha-form.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha-form.tsx @@ -3,8 +3,7 @@ import { useFormContext } from 'react-hook-form'; import { Typography } from '@mui/material'; import { FetchError } from '@/api/fetcher'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { CustomHCaptcha } from './h-captcha'; -import { type CustomHCaptchaRef } from './types'; +import { CustomHCaptcha, type CustomHCaptchaRef } from './h-captcha'; interface HCaptchaFormProps { name: string; diff --git a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha.tsx b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha.tsx index 2c65686351..1bcd5e3b4c 100644 --- a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/h-captcha.tsx @@ -2,7 +2,10 @@ import { forwardRef, useImperativeHandle, useRef } from 'react'; import HCaptcha from '@hcaptcha/react-hcaptcha'; import { env } from '@/shared/env'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { type CustomHCaptchaRef } from './types'; + +export interface CustomHCaptchaRef { + reset: () => void; +} interface CustomHCaptchaProps { onVerify: (token: string) => void; diff --git a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/index.ts b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/index.ts new file mode 100644 index 0000000000..0da9bf554a --- /dev/null +++ b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/index.ts @@ -0,0 +1 @@ +export * from './h-captcha-form'; diff --git a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/types.ts b/packages/apps/human-app/frontend/src/shared/components/hcaptcha/types.ts deleted file mode 100644 index dd95b93c46..0000000000 --- a/packages/apps/human-app/frontend/src/shared/components/hcaptcha/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface CustomHCaptchaRef { - reset: () => void; -} From 7225c79248e27b3cdeaff4f3b60d569f86f34de8 Mon Sep 17 00:00:00 2001 From: mpblocky <185767042+mpblocky@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:01:58 +0100 Subject: [PATCH 06/27] [HUMAN App] chore: move hcaptcha labeling hook to its module (#3128) --- .../modules/worker/hcaptcha-labeling/enable-labeler.page.tsx | 2 +- .../hooks}/enable-hcaptcha-labeling.ts | 5 +++-- .../src/modules/worker/hcaptcha-labeling/hooks/index.ts | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) rename packages/apps/human-app/frontend/src/modules/worker/{services => hcaptcha-labeling/hooks}/enable-hcaptcha-labeling.ts (96%) diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx index 3ddfdf07bd..338d41c3a8 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx @@ -4,13 +4,13 @@ import { t } from 'i18next'; import Typography from '@mui/material/Typography'; import { Navigate } from 'react-router-dom'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useEnableHCaptchaLabelingMutation } from '@/modules/worker/services/enable-hcaptcha-labeling'; import { Button } from '@/shared/components/ui/button'; import { PageCardError } from '@/shared/components/ui/page-card'; import { getErrorMessageForError } from '@/shared/errors'; import { breakpoints } from '@/shared/styles/breakpoints'; import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; import { routerPaths } from '@/router/router-paths'; +import { useEnableHCaptchaLabelingMutation } from './hooks'; export function EnableLabelerPage() { const isMobile = useIsMobile(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/enable-hcaptcha-labeling.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts similarity index 96% rename from packages/apps/human-app/frontend/src/modules/worker/services/enable-hcaptcha-labeling.ts rename to packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts index 9dbc4abd83..2d7cf8074e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/enable-hcaptcha-labeling.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts @@ -19,8 +19,7 @@ export function useEnableHCaptchaLabelingMutation() { const queryClient = useQueryClient(); const navigate = useNavigate(); const { refreshAccessTokenAsync } = useAccessTokenRefresh(); - - return useMutation({ + const mutation = useMutation({ mutationFn: async () => { const result = await apiClient( apiPaths.worker.enableHCaptchaLabeling.path, @@ -41,4 +40,6 @@ export function useEnableHCaptchaLabelingMutation() { await queryClient.invalidateQueries(); }, }); + + return mutation; } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts index 3c49984671..2029fc304c 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts @@ -1,2 +1,3 @@ export * from './use-daily-hmt-spent'; export * from './use-hcaptcha-user-stats'; +export * from './enable-hcaptcha-labeling'; From 76b128db800006247757b98deebe4d4b6fd54922 Mon Sep 17 00:00:00 2001 From: adrian-oleskiewicz Date: Wed, 26 Feb 2025 19:13:31 +0100 Subject: [PATCH 07/27] [HUMAN App] refactor: oracles table data (#2998) --- .../src/modules/smart-contracts/chains.ts | 4 +- .../jobs-discovery/oracles-table-mobile.tsx | 86 --------- .../jobs-discovery/oracles-table.tsx | 165 ------------------ .../worker/jobs-discovery/components/index.ts | 4 + .../components/oracles-table-desktop.tsx | 55 ++++++ .../components/oracles-table-item-mobile.tsx | 60 +++++++ .../oracles-table-job-types-select.tsx | 0 .../components/oracles-table.tsx | 72 ++++++++ .../worker/jobs-discovery/helpers/index.ts | 2 + .../helpers/is-hcaptcha-oracle.ts | 4 + .../should-navigate-to-registration.ts | 14 ++ .../worker/jobs-discovery/hooks/index.ts | 2 + .../hooks/use-get-oracles.ts} | 7 +- .../use-get-registration-data-oracles.ts} | 8 +- .../hooks/use-oracles-table-columns.tsx | 63 +++++++ .../hooks/use-select-oracle-navigation.ts | 46 +++++ .../modules/worker/jobs-discovery/index.ts | 2 + .../jobs-discovery/jobs-discovery.page.tsx | 23 +-- .../oracle-registration/registration.page.tsx | 2 +- .../modules/worker/views/jobs/jobs.page.tsx | 2 +- .../human-app/frontend/src/router/routes.tsx | 2 +- .../frontend/src/shared/i18n/en.json | 5 +- 22 files changed, 346 insertions(+), 282 deletions(-) delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table-mobile.tsx delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx rename packages/apps/human-app/frontend/src/modules/worker/{components/jobs-discovery => jobs-discovery/components}/oracles-table-job-types-select.tsx (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts rename packages/apps/human-app/frontend/src/modules/worker/{services/oracles.ts => jobs-discovery/hooks/use-get-oracles.ts} (91%) rename packages/apps/human-app/frontend/src/modules/worker/{services/get-registration-in-exchange-oracles.ts => jobs-discovery/hooks/use-get-registration-data-oracles.ts} (74%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts rename packages/apps/human-app/frontend/src/modules/worker/{views => }/jobs-discovery/jobs-discovery.page.tsx (50%) diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/chains.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/chains.ts index 107f8355d1..7666fcd23d 100644 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/chains.ts +++ b/packages/apps/human-app/frontend/src/modules/smart-contracts/chains.ts @@ -40,7 +40,7 @@ export const MainnetChainsIds = [ export type TestnetNarrow = Exclude; export type MainnetNarrow = Exclude; -export const TestnetChains: ChainWithAddresses[] = [ +export const TestnetChains: readonly ChainWithAddresses[] = [ { chainId: 80002, name: 'Amoy', @@ -50,7 +50,7 @@ export const TestnetChains: ChainWithAddresses[] = [ }, ]; -export const MainnetChains: ChainWithAddresses[] = [ +export const MainnetChains: readonly ChainWithAddresses[] = [ { chainId: 137, name: 'Polygon', diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table-mobile.tsx deleted file mode 100644 index 99324e5807..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table-mobile.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Grid, Paper, Stack, Typography } from '@mui/material'; -import { t } from 'i18next'; -import { Chips } from '@/shared/components/ui/chips'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { Loader } from '@/shared/components/ui/loader'; -import type { OraclesDataQueryResult } from '@/modules/worker/views/jobs-discovery/jobs-discovery.page'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; -import { ListItem } from '@/shared/components/ui/list-item'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import type { Oracle } from '@/modules/worker/services/oracles'; -import { NoRecords } from '@/shared/components/ui/no-records'; - -export function OraclesTableMobile({ - selectOracle, - oraclesQueryDataResult: { - data: oraclesData, - isError: isOraclesDataError, - isPending: isOraclesDataPending, - }, -}: { - selectOracle: (oracle: Oracle, jobTypes: string[]) => void; - oraclesQueryDataResult: OraclesDataQueryResult; -}) { - const { colorPalette } = useColorMode(); - - if (isOraclesDataPending) { - return ( - - - - ); - } - - if (isOraclesDataError) { - return ; - } - - return ( - - {oraclesData.map((d) => ( - - - - - - - {d.name} - - - - t(`jobTypeLabels.${jobType as JobType}`) - )} - /> - - - { - selectOracle(d, d.jobTypes); - }} - > - {t('worker.oraclesTable.seeJobs')} - - - ))} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table.tsx deleted file mode 100644 index 7e5ef2528d..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { t } from 'i18next'; -import type { MRT_ColumnDef } from 'material-react-table'; -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import { Grid } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; -import type { Oracle } from '@/modules/worker/services/oracles'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; -import { Chips } from '@/shared/components/ui/chips'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { routerPaths } from '@/router/router-paths'; -import type { OraclesDataQueryResult } from '@/modules/worker/views/jobs-discovery/jobs-discovery.page'; -import { env } from '@/shared/env'; -import { useGetRegistrationInExchangeOracles } from '@/modules/worker/services/get-registration-in-exchange-oracles'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { type JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { OraclesTableMobile } from '@/modules/worker/components/jobs-discovery/oracles-table-mobile'; - -const getColumns = ( - selectOracle: (oracle: Oracle) => void -): MRT_ColumnDef[] => { - return [ - { - accessorKey: 'name', - header: t('worker.oraclesTable.annotationTool'), - size: 100, - enableSorting: false, - }, - { - accessorKey: 'address', - header: t('worker.oraclesTable.oracleAddress'), - size: 100, - enableSorting: true, - Cell: (props) => , - }, - { - accessorKey: 'jobTypes', - header: t('worker.oraclesTable.jobTypes'), - size: 100, - enableSorting: false, - Cell: ({ row }) => { - const jobTypes: string[] = []; - for (const jobType of row.original.jobTypes) { - jobTypes.push(t(`jobTypeLabels.${jobType as JobType}`)); - } - return ; - }, - }, - { - accessorKey: 'url', - id: 'seeJobsAction', - header: '', - size: 100, - enableSorting: false, - Cell: (props) => { - return ( - - { - selectOracle(props.row.original); - }} - > - {t('worker.oraclesTable.seeJobs')} - - - ); - }, - }, - ]; -}; - -export function OraclesTable({ - oraclesQueryDataResult, -}: { - oraclesQueryDataResult: OraclesDataQueryResult; -}) { - const { colorPalette, isDarkMode } = useColorMode(); - const { - data: oraclesData, - isError: isOraclesDataError, - isPending: isOraclesDataPending, - } = oraclesQueryDataResult; - const navigate = useNavigate(); - const isMobile = useIsMobile(); - const { user } = useAuthenticatedUser(); - const { data: registrationInExchangeOraclesResult } = - useGetRegistrationInExchangeOracles(); - - const selectOracle = (oracle: Oracle) => { - if ( - oracle.registrationNeeded && - !registrationInExchangeOraclesResult?.oracle_addresses.find( - (address) => address === oracle.address - ) - ) { - navigate( - `${routerPaths.worker.registrationInExchangeOracle}/${oracle.address}` - ); - return; - } - - if (oracle.address === env.VITE_H_CAPTCHA_ORACLE_ADDRESS) { - if (!user.site_key) { - navigate(routerPaths.worker.enableLabeler); - return; - } - navigate(routerPaths.worker.HcaptchaLabeling); - return; - } - navigate(`${routerPaths.worker.jobs}/${oracle.address}`, { - state: { - oracle, - }, - }); - }; - - const table = useMaterialReactTable({ - state: { - isLoading: isOraclesDataPending, - showAlertBanner: isOraclesDataError, - }, - columns: getColumns(selectOracle), - data: oraclesData ?? [], - enableColumnActions: false, - enableColumnFilters: false, - enableSorting: false, - enablePagination: false, - enableTopToolbar: false, - enableBottomToolbar: false, - muiTableHeadCellProps: { - sx: { - borderColor: colorPalette.paper.text, - }, - }, - muiTableBodyCellProps: { - sx: { - borderColor: colorPalette.paper.text, - }, - }, - muiTablePaperProps: { - sx: { - boxShadow: '0px 2px 2px 0px #E9EBFA80', - }, - }, - ...(isDarkMode ? createTableDarkMode(colorPalette) : {}), - }); - - return ( - <> - {isMobile ? ( - - ) : ( - - )} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts new file mode 100644 index 0000000000..9bc965d5af --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts @@ -0,0 +1,4 @@ +export * from './oracles-table-job-types-select'; +export * from './oracles-table'; +export * from './oracles-table-item-mobile'; +export * from './oracles-table-desktop'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx new file mode 100644 index 0000000000..3314b469ee --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx @@ -0,0 +1,55 @@ +import { + MaterialReactTable, + useMaterialReactTable, +} from 'material-react-table'; +import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useOraclesTableColumns } from '../hooks/use-oracles-table-columns'; +import { type Oracle } from '../hooks'; + +interface OraclesTableDesktopProps { + isOraclesDataPending: boolean; + isOraclesDataError: boolean; + oraclesData: Oracle[]; +} + +export function OraclesTableDesktop({ + isOraclesDataPending, + isOraclesDataError, + oraclesData, +}: Readonly) { + const { colorPalette, isDarkMode } = useColorMode(); + const tableColumns = useOraclesTableColumns(); + const table = useMaterialReactTable({ + state: { + isLoading: isOraclesDataPending, + showAlertBanner: isOraclesDataError, + }, + columns: tableColumns, + data: oraclesData, + enableColumnActions: false, + enableColumnFilters: false, + enableSorting: false, + enablePagination: false, + enableTopToolbar: false, + enableBottomToolbar: false, + muiTableHeadCellProps: { + sx: { + borderColor: colorPalette.paper.text, + }, + }, + muiTableBodyCellProps: { + sx: { + borderColor: colorPalette.paper.text, + }, + }, + muiTablePaperProps: { + sx: { + boxShadow: '0px 2px 2px 0px #E9EBFA80', + }, + }, + ...(isDarkMode ? createTableDarkMode(colorPalette) : {}), + }); + + return ; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx new file mode 100644 index 0000000000..44566c107d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx @@ -0,0 +1,60 @@ +import { Grid, Paper, type SxProps, Typography } from '@mui/material'; +import { t } from 'i18next'; +import { Chips } from '@/shared/components/ui/chips'; +import { TableButton } from '@/shared/components/ui/table-button'; +import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; +import { ListItem } from '@/shared/components/ui/list-item'; +import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { type Oracle } from '../hooks'; +import { useSelectOracleNavigation } from '../hooks/use-select-oracle-navigation'; + +interface OraclesTableItemMobileProps { + oracle: Oracle; +} + +const styles: SxProps = { + px: '16px', + py: '32px', + marginBottom: '20px', + borderRadius: '20px', + display: 'flex', + flexDirection: 'column', + gap: '1rem', + boxShadow: 'none', +}; + +export function OraclesTableItemMobile({ + oracle, +}: Readonly) { + const { colorPalette } = useColorMode(); + const { selectOracle } = useSelectOracleNavigation(); + + return ( + + + + + + + {oracle.name} + + + + t(`jobTypeLabels.${jobType as JobType}`) + )} + /> + + + { + selectOracle(oracle); + }} + > + {t('worker.oraclesTable.seeJobs')} + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table-job-types-select.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-job-types-select.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs-discovery/oracles-table-job-types-select.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-job-types-select.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx new file mode 100644 index 0000000000..b4cd800329 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx @@ -0,0 +1,72 @@ +import { Stack, Grid } from '@mui/material'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { Loader } from '@/shared/components/ui/loader'; +import { NoRecords } from '@/shared/components/ui/no-records'; +import { useGetOraclesNotifications } from '@/modules/worker/hooks/use-get-oracles-notifications'; +import { PageCardError } from '@/shared/components/ui/page-card'; +import { useGetOracles } from '../hooks'; +import { OraclesTableItemMobile } from './oracles-table-item-mobile'; +import { OraclesTableDesktop } from './oracles-table-desktop'; + +export function OraclesTable() { + const isMobile = useIsMobile(); + const { t } = useTranslation(); + const { onError } = useGetOraclesNotifications(); + const { + data: oraclesData, + isError: isOraclesDataError, + isPending: isOraclesDataPending, + error: oraclesDataError, + } = useGetOracles(); + + useEffect(() => { + if (oraclesDataError) { + onError(oraclesDataError); + } + }, [oraclesDataError, onError]); + + if (isOraclesDataPending) { + return ( + + + + ); + } + + if (isOraclesDataError) { + return ( + + ); + } + + if (!oraclesData.length) { + return ; + } + + return isMobile ? ( + + {oraclesData.map((oracle) => ( + + ))} + + ) : ( + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts new file mode 100644 index 0000000000..7db6869470 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './is-hcaptcha-oracle'; +export * from './should-navigate-to-registration'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts new file mode 100644 index 0000000000..c9c3903a60 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts @@ -0,0 +1,4 @@ +import { env } from '@/shared/env'; + +export const isHCaptchaOracle = (address: string): boolean => + address === env.VITE_H_CAPTCHA_ORACLE_ADDRESS; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts new file mode 100644 index 0000000000..90af1f932c --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts @@ -0,0 +1,14 @@ +import { type Oracle } from '../hooks'; + +interface RegistrationResult { + oracle_addresses: string[]; +} + +export const shouldNavigateToRegistration = ( + oracle: Oracle, + registrationData?: RegistrationResult +): boolean => + Boolean( + oracle.registrationNeeded && + !registrationData?.oracle_addresses.includes(oracle.address) + ); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts new file mode 100644 index 0000000000..ba8a564805 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './use-get-oracles'; +export * from './use-oracles-table-columns'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/oracles.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-oracles.ts similarity index 91% rename from packages/apps/human-app/frontend/src/modules/worker/services/oracles.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-oracles.ts index c2d1004cde..a4f888de54 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/oracles.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-oracles.ts @@ -6,9 +6,11 @@ import { apiPaths } from '@/api/api-paths'; import { useJobsTypesOraclesFilter } from '@/modules/worker/hooks/use-job-types-oracles-table'; import { stringifyUrlQueryObject } from '@/shared/helpers/transfomers'; import { env } from '@/shared/env'; +import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains'; const OracleSchema = z.object({ address: z.string(), + chainId: z.number(), role: z.string(), url: z.string(), jobTypes: z.array(z.string()), @@ -37,8 +39,11 @@ for (const [oracleName, oracleUrls] of Object.entries(OracleNameToUrls)) { } } +const isTestnet = env.VITE_NETWORK === 'testnet'; + const H_CAPTCHA_ORACLE: Oracle = { address: env.VITE_H_CAPTCHA_ORACLE_ADDRESS, + chainId: isTestnet ? TestnetChains[0].chainId : MainnetChains[0].chainId, jobTypes: env.VITE_H_CAPTCHA_ORACLE_TASK_TYPES, role: env.VITE_H_CAPTCHA_ORACLE_ROLE, url: env.VITE_H_CAPTCHA_ORACLE_ANNOTATION_TOOL, @@ -46,7 +51,7 @@ const H_CAPTCHA_ORACLE: Oracle = { registrationNeeded: false, }; -export async function getOracles({ +async function getOracles({ selected_job_types, signal, }: { diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/get-registration-in-exchange-oracles.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-registration-data-oracles.ts similarity index 74% rename from packages/apps/human-app/frontend/src/modules/worker/services/get-registration-in-exchange-oracles.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-registration-data-oracles.ts index 177c86cf21..cadcceaa27 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/get-registration-in-exchange-oracles.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-registration-data-oracles.ts @@ -8,7 +8,7 @@ const RegisteredOraclesSuccessResponseSchema = z.object({ oracle_addresses: z.array(z.string()), }); -export async function getRegistrationInExchangeOracles() { +async function getRegistrationDataInOracles() { return apiClient(apiPaths.worker.registrationInExchangeOracle.path, { authenticated: true, successSchema: RegisteredOraclesSuccessResponseSchema, @@ -16,10 +16,10 @@ export async function getRegistrationInExchangeOracles() { }); } -export function useGetRegistrationInExchangeOracles() { +export function useGetRegistrationDataInOracles() { return useQuery({ - queryFn: () => getRegistrationInExchangeOracles(), - queryKey: ['getRegistrationInExchangeOracles'], + queryFn: () => getRegistrationDataInOracles(), + queryKey: ['getRegistrationDataInOracles'], refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx new file mode 100644 index 0000000000..4dbafaec6d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx @@ -0,0 +1,63 @@ +import { useMemo } from 'react'; +import { t } from 'i18next'; +import { Grid } from '@mui/material'; +import type { MRT_ColumnDef } from 'material-react-table'; +import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; +import { Chips } from '@/shared/components/ui/chips'; +import { TableButton } from '@/shared/components/ui/table-button'; +import { type JobType } from '@/modules/smart-contracts/EthKVStore/config'; +import { type Oracle } from './use-get-oracles'; +import { useSelectOracleNavigation } from './use-select-oracle-navigation'; + +export const useOraclesTableColumns = (): MRT_ColumnDef[] => { + const { selectOracle } = useSelectOracleNavigation(); + + return useMemo( + () => [ + { + accessorKey: 'name', + header: t('worker.oraclesTable.annotationTool'), + size: 100, + enableSorting: false, + }, + { + accessorKey: 'address', + header: t('worker.oraclesTable.oracleAddress'), + size: 100, + enableSorting: true, + Cell: (props) => , + }, + { + accessorKey: 'jobTypes', + header: t('worker.oraclesTable.jobTypes'), + size: 100, + enableSorting: false, + Cell: ({ row }) => { + const jobTypes = row.original.jobTypes.map((jobType) => + t(`jobTypeLabels.${jobType as JobType}`) + ); + return ; + }, + }, + { + accessorKey: 'url', + id: 'seeJobsAction', + header: '', + size: 100, + enableSorting: false, + Cell: (props) => ( + + { + selectOracle(props.row.original); + }} + > + {t('worker.oraclesTable.seeJobs')} + + + ), + }, + ], + [selectOracle] + ); +}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts new file mode 100644 index 0000000000..f3d90b872e --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts @@ -0,0 +1,46 @@ +import { useNavigate } from 'react-router-dom'; +import { useCallback, useMemo } from 'react'; +import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; +import { routerPaths } from '@/router/router-paths'; +import { shouldNavigateToRegistration, isHCaptchaOracle } from '../helpers'; +import { useGetRegistrationDataInOracles } from './use-get-registration-data-oracles'; +import { type Oracle } from './use-get-oracles'; + +const getHCaptchaPagePath = (siteKey: string | null | undefined): string => + siteKey + ? routerPaths.worker.HcaptchaLabeling + : routerPaths.worker.enableLabeler; + +export const useSelectOracleNavigation = () => { + const navigate = useNavigate(); + const { user } = useAuthenticatedUser(); + const { data } = useGetRegistrationDataInOracles(); + + const hCaptchaPagePath = useMemo( + () => getHCaptchaPagePath(user.site_key), + [user.site_key] + ); + + const selectOracle = useCallback( + (oracle: Oracle) => { + if (shouldNavigateToRegistration(oracle, data)) { + navigate( + `${routerPaths.worker.registrationInExchangeOracle}/${oracle.address}` + ); + return; + } + + if (isHCaptchaOracle(oracle.address)) { + navigate(hCaptchaPagePath); + return; + } + + navigate(`${routerPaths.worker.jobs}/${oracle.address}`, { + state: { oracle }, + }); + }, + [data, navigate, hCaptchaPagePath] + ); + + return { selectOracle }; +}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts new file mode 100644 index 0000000000..e171b27f6d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts @@ -0,0 +1,2 @@ +export * from './jobs-discovery.page'; +export * from './hooks/use-get-oracles'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/jobs-discovery/jobs-discovery.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx similarity index 50% rename from packages/apps/human-app/frontend/src/modules/worker/views/jobs-discovery/jobs-discovery.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx index 5969f52d3c..b844b46d0b 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/jobs-discovery/jobs-discovery.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx @@ -1,32 +1,15 @@ import Paper from '@mui/material/Paper'; import Grid from '@mui/material/Grid'; -import type { UseQueryResult } from '@tanstack/react-query'; import { Navigate } from 'react-router-dom'; -import { useEffect, useRef } from 'react'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import type { Oracle } from '@/modules/worker/services/oracles'; -import { useGetOracles } from '@/modules/worker/services/oracles'; -import { routerPaths } from '@/router/router-paths'; -import { useGetOraclesNotifications } from '@/modules/worker/hooks/use-get-oracles-notifications'; import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { OraclesTableJobTypesSelect } from '@/modules/worker/components/jobs-discovery/oracles-table-job-types-select'; -import { OraclesTable } from '@/modules/worker/components/jobs-discovery/oracles-table'; - -export type OraclesDataQueryResult = UseQueryResult; +import { routerPaths } from '@/router/router-paths'; +import { OraclesTableJobTypesSelect, OraclesTable } from './components'; export function JobsDiscoveryPage() { - const { onError } = useGetOraclesNotifications(); - const onErrorRef = useRef(onError); - const oraclesQueryResult = useGetOracles(); const isMobile = useIsMobile(); const { user } = useAuthenticatedUser(); - useEffect(() => { - if (oraclesQueryResult.error) { - onErrorRef.current(oraclesQueryResult.error); - } - }, [oraclesQueryResult.error]); - if (user.kyc_status !== 'approved') { return ; } @@ -44,7 +27,7 @@ export function JobsDiscoveryPage() { }} > - + diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx index e8288240b3..a13c018cf5 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx @@ -4,7 +4,7 @@ import { Navigate, useParams } from 'react-router-dom'; import { RegistrationForm } from '@/modules/worker/oracle-registration/registration-form'; import { Loader } from '@/shared/components/ui/loader'; import { routerPaths } from '@/router/router-paths'; -import { useGetOracles } from '../services/oracles'; +import { useGetOracles } from '../jobs-discovery'; import { useIsAlreadyRegistered } from './hooks'; function isAddress(address: string | undefined): address is string { diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx index 6a91be4441..31aa77491f 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx @@ -10,7 +10,7 @@ import { MyJobsTableMobile } from '@/modules/worker/components/jobs/my-jobs/mobi import { AvailableJobsTable } from '@/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table'; import { MyJobsDrawerMobile } from '@/modules/worker/components/jobs/my-jobs/mobile/my-jobs-drawer-mobile'; import { AvailableJobsDrawerMobile } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-drawer-mobile'; -import { useGetOracles } from '@/modules/worker/services/oracles'; +import { useGetOracles } from '@/modules/worker/jobs-discovery'; import { useGetUiConfig } from '@/modules/worker/services/get-ui-config'; import { useColorMode } from '@/shared/contexts/color-mode'; import { useGetOraclesNotifications } from '@/modules/worker/hooks/use-get-oracles-notifications'; diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx index 2c1f9dc3d9..0d8297db83 100644 --- a/packages/apps/human-app/frontend/src/router/routes.tsx +++ b/packages/apps/human-app/frontend/src/router/routes.tsx @@ -5,7 +5,6 @@ import { SendResetLinkWorkerSuccessPage } from '@/modules/worker/views/send-rese import { ResetPasswordWorkerPage } from '@/modules/worker/views/reset-password/reset-password.page'; import { SendResetLinkWorkerPage } from '@/modules/worker/views/send-reset-link/send-reset-link.page'; import { ResetPasswordWorkerSuccessPage } from '@/modules/worker/views/reset-password/reset-password-success.page'; -import { JobsDiscoveryPage } from '@/modules/worker/views/jobs-discovery/jobs-discovery.page'; import { JobsPage } from '@/modules/worker/views/jobs/jobs.page'; import { env } from '@/shared/env'; import { RegistrationPage } from '@/modules/worker/oracle-registration'; @@ -33,6 +32,7 @@ import { } from '@/modules/worker/hcaptcha-labeling'; import { SignUpWorkerPage } from '@/modules/signup/worker'; import { SignInWorkerPage } from '@/modules/signin/worker'; +import { JobsDiscoveryPage } from '@/modules/worker/jobs-discovery'; export const unprotectedRoutes: RouteProps[] = [ { diff --git a/packages/apps/human-app/frontend/src/shared/i18n/en.json b/packages/apps/human-app/frontend/src/shared/i18n/en.json index 6afaf11bf1..3ec5cc3083 100644 --- a/packages/apps/human-app/frontend/src/shared/i18n/en.json +++ b/packages/apps/human-app/frontend/src/shared/i18n/en.json @@ -217,7 +217,10 @@ "oracleAddress": "Oracle address", "jobTypes": "Task types", "seeJobs": "See Tasks", - "allJobs": "All tasks" + "allJobs": "All tasks", + "error": { + "gettingOracles": "There was an error while fetching oracles, please try again later." + } }, "registrationInExchangeOracle": { "requiredMessage": "This oracle requires a registration process. Click on the button below to see the registration tutorial:", From cdc1d89f8ce8a28c36845f6fc6c59b793e640992 Mon Sep 17 00:00:00 2001 From: adrian-oleskiewicz Date: Thu, 27 Feb 2025 11:33:42 +0100 Subject: [PATCH 08/27] [HUMAN App] refactor: profile module (#3004) --- .../components/profile/profile-actions.tsx | 111 ------------------ .../profile/profile-email-notifications.tsx | 25 ---- .../profile/components/buttons/index.ts | 2 + .../buttons}/register-address-btn.tsx | 0 .../components/buttons/start-kyc-btn.tsx | 26 ++++ .../worker/profile/components/index.ts | 3 + .../components/kyc-verification-control.tsx | 19 +++ .../profile/components/profile-actions.tsx | 17 +++ .../components}/profile-data.tsx | 0 .../components/status-labels}/done-label.tsx | 2 +- .../components/status-labels}/error-label.tsx | 0 .../profile/components/status-labels/index.ts | 2 + .../components}/wallet-connect-done.tsx | 0 .../components/wallet-connection-control.tsx | 48 ++++++++ .../src/modules/worker/profile/hooks/index.ts | 3 + .../hooks/use-start-kyc.ts} | 36 ++---- .../hooks/use-worker-profile-status.tsx | 16 +++ .../hooks/use-worker-wallet-registration.ts | 27 +++++ .../src/modules/worker/profile/index.ts | 1 + .../src/modules/worker/profile/types/index.ts | 1 + .../worker/profile/types/profile-types.ts | 5 + .../views}/profile.page.tsx | 25 ++-- .../human-app/frontend/src/router/routes.tsx | 2 +- 23 files changed, 199 insertions(+), 172 deletions(-) delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-actions.tsx delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-email-notifications.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts rename packages/apps/human-app/frontend/src/modules/worker/{components/profile => profile/components/buttons}/register-address-btn.tsx (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-kyc-btn.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/kyc-verification-control.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-actions.tsx rename packages/apps/human-app/frontend/src/modules/worker/{components/profile => profile/components}/profile-data.tsx (100%) rename packages/apps/human-app/frontend/src/modules/worker/{components/profile => profile/components/status-labels}/done-label.tsx (87%) rename packages/apps/human-app/frontend/src/modules/worker/{components/profile => profile/components/status-labels}/error-label.tsx (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/index.ts rename packages/apps/human-app/frontend/src/modules/worker/{components/profile => profile/components}/wallet-connect-done.tsx (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts rename packages/apps/human-app/frontend/src/modules/worker/{components/profile/start-kyc-btn.tsx => profile/hooks/use-start-kyc.ts} (60%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-wallet-registration.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/types/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts rename packages/apps/human-app/frontend/src/modules/worker/{components/profile => profile/views}/profile.page.tsx (79%) diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-actions.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-actions.tsx deleted file mode 100644 index 8f4eee2683..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-actions.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import Grid from '@mui/material/Grid'; -import { useTranslation } from 'react-i18next'; -import { Navigate } from 'react-router-dom'; -import { useEffect, useRef } from 'react'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { Button } from '@/shared/components/ui/button'; -import { routerPaths } from '@/router/router-paths'; -import { WalletConnectDone } from '@/modules/worker/components/profile/wallet-connect-done'; -import { StartKycButton } from '@/modules/worker/components/profile/start-kyc-btn'; -import { RegisterAddressBtn } from '@/modules/worker/components/profile/register-address-btn'; -import { DoneLabel } from '@/modules/worker/components/profile/done-label'; -import { useRegisterAddressNotifications } from '@/modules/worker/hooks/use-register-address-notifications'; -import { useRegisterAddress } from '@/modules/worker/hooks/use-register-address'; -import { ErrorLabel } from './error-label'; - -export function ProfileActions() { - const { - isConnected: isWalletConnected, - address, - openModal, - } = useWalletConnect(); - const { onSuccess, onError } = useRegisterAddressNotifications(); - const { - mutate: registerAddressMutation, - isPending: isRegisterAddressMutationPending, - } = useRegisterAddress({ - onError, - onSuccess, - }); - const modalWasOpened = useRef(false); - useEffect(() => { - if (isWalletConnected && modalWasOpened.current) { - registerAddressMutation(); - } - }, [address, isWalletConnected, registerAddressMutation]); - const { user } = useAuthenticatedUser(); - const { t } = useTranslation(); - const emailVerified = user.status === 'active'; - const kycApproved = user.kyc_status === 'approved'; - const kycDeclined = user.kyc_status === 'declined'; - const kycToComplete = !(kycApproved || kycDeclined); - - const getConnectWalletBtn = () => { - switch (true) { - case !kycApproved: - return ( - - ); - case !user.wallet_address && isWalletConnected: - case Boolean(user.wallet_address): - return ( - - - - ); - case !user.wallet_address && !isWalletConnected: - return ( - - ); - default: - return null; - } - }; - - if (!emailVerified) { - return ( - - ); - } - - return ( - - - {kycApproved && ( - {t('worker.profile.kycCompleted')} - )} - {kycDeclined && ( - {t('worker.profile.kycDeclined')} - )} - {kycToComplete && } - - {getConnectWalletBtn()} - {kycApproved && !user.wallet_address && isWalletConnected ? ( - - - - ) : null} - {kycApproved && !user.wallet_address ? ( - {t('worker.profile.walletAddressMessage')} - ) : null} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-email-notifications.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-email-notifications.tsx deleted file mode 100644 index 4a575fa43e..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-email-notifications.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Grid from '@mui/material/Grid'; -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import Stack from '@mui/material/Stack'; -import Switch from '@mui/material/Switch'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; - -export function ProfileEmailNotification() { - const { user } = useAuthenticatedUser(); - const { t } = useTranslation(); - - return ( - - - {t('worker.profile.emailNotifications')} - - - - {t('worker.profile.notificationsConsent')} - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts new file mode 100644 index 0000000000..ebfc0e1758 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts @@ -0,0 +1,2 @@ +export * from './start-kyc-btn'; +export * from './register-address-btn'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/register-address-btn.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/register-address-btn.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/register-address-btn.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/register-address-btn.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-kyc-btn.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-kyc-btn.tsx new file mode 100644 index 0000000000..88c5363ecc --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-kyc-btn.tsx @@ -0,0 +1,26 @@ +import { t } from 'i18next'; +import { Button } from '@/shared/components/ui/button'; +import { useStartKyc } from '../../hooks'; + +export function StartKycBtn() { + const { isKYCInProgress, kycStartIsPending, startKYC } = useStartKyc(); + + if (isKYCInProgress) { + return ( + + ); + } + + return ( + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts new file mode 100644 index 0000000000..43ff1e202d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts @@ -0,0 +1,3 @@ +export * from './wallet-connect-done'; +export * from './profile-actions'; +export * from './profile-data'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/kyc-verification-control.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/kyc-verification-control.tsx new file mode 100644 index 0000000000..21f661e3be --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/kyc-verification-control.tsx @@ -0,0 +1,19 @@ +import { useTranslation } from 'react-i18next'; +import { useWorkerKycStatus } from '../hooks'; +import { StartKycBtn } from './buttons'; +import { DoneLabel, ErrorLabel } from './status-labels'; + +export function KycVerificationControl() { + const { t } = useTranslation(); + const { kycApproved, kycDeclined, kycToComplete } = useWorkerKycStatus(); + + return ( + <> + {kycApproved && {t('worker.profile.kycCompleted')}} + {kycDeclined && ( + {t('worker.profile.kycDeclined')} + )} + {kycToComplete && } + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-actions.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-actions.tsx new file mode 100644 index 0000000000..9b37cf4107 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-actions.tsx @@ -0,0 +1,17 @@ +import Grid from '@mui/material/Grid'; +import { KycVerificationControl } from './kyc-verification-control'; +import { WalletConnectionControl } from './wallet-connection-control'; + +export function ProfileActions() { + return ( + + + + + + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-data.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-data.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/profile-data.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-data.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/done-label.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/done-label.tsx similarity index 87% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/done-label.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/done-label.tsx index a410132b82..cbd5b38c55 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/profile/done-label.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/done-label.tsx @@ -6,7 +6,7 @@ interface DoneLabelProps { children: string | React.ReactElement; } -export function DoneLabel({ children }: DoneLabelProps) { +export function DoneLabel({ children }: Readonly) { if (typeof children === 'string') { return ( diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/error-label.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/error-label.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/error-label.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/error-label.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/index.ts new file mode 100644 index 0000000000..319821880d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/status-labels/index.ts @@ -0,0 +1,2 @@ +export * from './done-label'; +export * from './error-label'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/wallet-connect-done.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connect-done.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/wallet-connect-done.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connect-done.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx new file mode 100644 index 0000000000..148ddb41fb --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx @@ -0,0 +1,48 @@ +import { useTranslation } from 'react-i18next'; +import { Grid } from '@mui/material'; +import { Button } from '@/shared/components/ui/button'; +import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; +import { useWorkerKycStatus, useWorkerWalletRegistration } from '../hooks'; +import { DoneLabel } from './status-labels'; +import { WalletConnectDone } from './wallet-connect-done'; +import { RegisterAddressBtn } from './buttons'; + +export function WalletConnectionControl() { + const { t } = useTranslation(); + const { user } = useAuthenticatedUser(); + const { kycApproved } = useWorkerKycStatus(); + const { isConnected, isRegisterAddressPending, handleConnectWallet } = + useWorkerWalletRegistration(); + + const { wallet_address: walletAddress } = user; + const hasWalletAddress = Boolean(walletAddress); + + if (hasWalletAddress) { + return ( + + + + ); + } + + if (kycApproved && isConnected) { + return ( + + + {t('worker.profile.walletAddressMessage')} + + ); + } + + return ( + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts new file mode 100644 index 0000000000..a30beaa0d3 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './use-worker-wallet-registration'; +export * from './use-worker-profile-status'; +export * from './use-start-kyc'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/start-kyc-btn.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts similarity index 60% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/start-kyc-btn.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts index 31fe800abd..6ab0781f4b 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/profile/start-kyc-btn.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts @@ -1,14 +1,10 @@ -import { t } from 'i18next'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { useKycStartMutation } from '@/modules/worker/services/get-kyc-session-id'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { Button } from '@/shared/components/ui/button'; import { useKycErrorNotifications } from '@/modules/worker/hooks/use-kyc-notification'; import { FetchError } from '@/api/fetcher'; -export function StartKycButton() { +export function useStartKyc() { const [isKYCInProgress, setIsKYCInProgress] = useState(false); - const { user } = useAuthenticatedUser(); const onError = useKycErrorNotifications(); const { data: kycStartData, @@ -18,9 +14,9 @@ export function StartKycButton() { error: kycStartMutationError, } = useKycStartMutation(); - const startKYC = () => { + const startKYC = useCallback(() => { kycStartMutation(); - }; + }, [kycStartMutation]); useEffect(() => { if (kycStartMutationStatus === 'error') { @@ -44,23 +40,9 @@ export function StartKycButton() { onError, ]); - if (isKYCInProgress) { - return ( - - ); - } - - return ( - - ); + return { + isKYCInProgress, + kycStartIsPending, + startKYC, + }; } diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx new file mode 100644 index 0000000000..23517e0d1b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx @@ -0,0 +1,16 @@ +import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; +import { type WorkerProfileStatus } from '../types'; + +export function useWorkerKycStatus(): WorkerProfileStatus { + const { user } = useAuthenticatedUser(); + + const kycApproved = user.kyc_status === 'approved'; + const kycDeclined = user.kyc_status === 'declined'; + const kycToComplete = !(kycApproved || kycDeclined); + + return { + kycApproved, + kycDeclined, + kycToComplete, + }; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-wallet-registration.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-wallet-registration.ts new file mode 100644 index 0000000000..d6883fa903 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-wallet-registration.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; +import { useWalletConnect } from '@/shared/contexts/wallet-connect'; +import { useRegisterAddress } from '@/modules/worker/hooks/use-register-address'; +import { useRegisterAddressNotifications } from '../../hooks/use-register-address-notifications'; + +export function useWorkerWalletRegistration() { + const { isConnected, openModal } = useWalletConnect(); + const { onSuccess, onError } = useRegisterAddressNotifications(); + const { + mutate: registerAddressMutation, + isPending: isRegisterAddressPending, + } = useRegisterAddress({ + onError, + onSuccess, + }); + + const handleConnectWallet = useCallback(() => { + void openModal(); + registerAddressMutation(); + }, [openModal, registerAddressMutation]); + + return { + isConnected, + isRegisterAddressPending, + handleConnectWallet, + }; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/index.ts new file mode 100644 index 0000000000..5324bd7f96 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/index.ts @@ -0,0 +1 @@ +export * from './views/profile.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/types/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/types/index.ts new file mode 100644 index 0000000000..99448194ce --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/types/index.ts @@ -0,0 +1 @@ +export * from './profile-types'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts new file mode 100644 index 0000000000..5b4fd9c664 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts @@ -0,0 +1,5 @@ +export interface WorkerProfileStatus { + kycApproved: boolean; + kycDeclined: boolean; + kycToComplete: boolean; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx similarity index 79% rename from packages/apps/human-app/frontend/src/modules/worker/components/profile/profile.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx index 56eb56ff43..b2acf65b62 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/profile/profile.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx @@ -1,15 +1,16 @@ import { Grid, Paper } from '@mui/material'; import { useEffect } from 'react'; import { t } from 'i18next'; -import { ProfileData } from '@/modules/worker/components/profile/profile-data'; -import { ProfileActions } from '@/modules/worker/components/profile/profile-actions'; +import { useNavigate } from 'react-router-dom'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; +import { useWalletConnect } from '@/shared/contexts/wallet-connect'; import { TopNotificationType, useNotification, } from '@/shared/hooks/use-notification'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; +import { routerPaths } from '@/router/router-paths'; +import { ProfileData, ProfileActions } from '../components'; export function WorkerProfilePage() { const { user } = useAuthenticatedUser(); @@ -17,6 +18,18 @@ export function WorkerProfilePage() { const { isConnected, initializing, web3ProviderMutation } = useWalletConnect(); const { showNotification } = useNotification(); + const navigate = useNavigate(); + + const emailVerified = user.status === 'active'; + + useEffect(() => { + if (!emailVerified) { + navigate(routerPaths.worker.verifyEmail, { + replace: true, + state: { routerState: { email: user.email } }, + }); + } + }, [navigate, user.email, emailVerified]); useEffect(() => { if (initializing) return; @@ -65,8 +78,6 @@ export function WorkerProfilePage() { > - {/* TODO add email notifications toggling */} - {/* */} ); diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx index 0d8297db83..7f0a411843 100644 --- a/packages/apps/human-app/frontend/src/router/routes.tsx +++ b/packages/apps/human-app/frontend/src/router/routes.tsx @@ -8,7 +8,6 @@ import { ResetPasswordWorkerSuccessPage } from '@/modules/worker/views/reset-pas import { JobsPage } from '@/modules/worker/views/jobs/jobs.page'; import { env } from '@/shared/env'; import { RegistrationPage } from '@/modules/worker/oracle-registration'; -import { WorkerProfilePage } from '@/modules/worker/components/profile/profile.page'; import { HandIcon, ProfileIcon, @@ -33,6 +32,7 @@ import { import { SignUpWorkerPage } from '@/modules/signup/worker'; import { SignInWorkerPage } from '@/modules/signin/worker'; import { JobsDiscoveryPage } from '@/modules/worker/jobs-discovery'; +import { WorkerProfilePage } from '@/modules/worker/profile'; export const unprotectedRoutes: RouteProps[] = [ { From 3501c198136916b0f04eb1202a2a3c67d4d0ed03 Mon Sep 17 00:00:00 2001 From: Dmitry Nechay Date: Thu, 27 Feb 2025 13:34:45 +0300 Subject: [PATCH 09/27] [Reputation Oracle] refactor: web3service and dependent configs (#3115) --- .../server/src/app.module.ts | 38 +-- .../server/src/common/constants/networks.ts | 11 - .../server/src/common/enums/role.ts | 5 - .../server/src/common/enums/web3.ts | 6 - .../src/common/guards/signature.auth.spec.ts | 246 ++++++++++++------ .../src/common/guards/signature.auth.ts | 34 ++- .../server/src/config/config.module.ts | 8 +- .../server/src/config/env-schema.ts | 3 +- .../src/config/network-config.service.ts | 112 -------- .../server/src/config/web3-config.service.ts | 52 +++- .../server/src/database/database.module.ts | 10 +- .../integrations/hcaptcha/hcaptcha.module.ts | 3 +- .../apps/reputation-oracle/server/src/main.ts | 4 +- .../src/modules/auth/auth.controller.ts | 2 +- .../server/src/modules/auth/auth.module.ts | 17 +- .../src/modules/auth/auth.service.spec.ts | 18 +- .../server/src/modules/auth/auth.service.ts | 45 +--- .../src/modules/cron-job/cron-job.module.ts | 11 +- .../server/src/modules/email/module.ts | 3 +- .../escrow-completion.module.ts | 15 +- .../escrow-completion.service.spec.ts | 1 - .../escrow-completion.service.ts | 13 +- .../src/modules/health/health.module.ts | 3 +- .../src/modules/kyc/kyc.controller.spec.ts | 79 ------ .../server/src/modules/kyc/kyc.module.ts | 9 +- .../src/modules/kyc/kyc.service.spec.ts | 16 +- .../server/src/modules/kyc/kyc.service.ts | 8 +- .../src/modules/payout/payout.module.ts | 6 +- .../qualification.controller.spec.ts | 146 ----------- .../qualification/qualification.module.ts | 10 +- .../reputation/reputation.controller.spec.ts | 121 --------- .../modules/reputation/reputation.module.ts | 10 +- .../reputation/reputation.service.spec.ts | 10 +- .../modules/reputation/reputation.service.ts | 10 +- .../src/modules/storage/storage.module.ts | 4 +- .../src/modules/user/user.controller.ts | 28 +- .../server/src/modules/user/user.module.ts | 10 +- .../src/modules/user/user.service.spec.ts | 34 +-- .../server/src/modules/user/user.service.ts | 70 ++--- .../server/src/modules/web3/web3.module.ts | 3 +- .../src/modules/web3/web3.service.spec.ts | 129 ++++++--- .../server/src/modules/web3/web3.service.ts | 88 +++++-- .../webhook/webhook-incoming.module.ts | 9 +- .../webhook/webhook-incoming.service.spec.ts | 1 - .../webhook/webhook-outgoing.module.ts | 7 +- .../webhook/webhook-outgoing.service.spec.ts | 1 - .../src/modules/webhook/webhook.controller.ts | 6 +- .../server/src/utils/backoff.spec.ts | 2 +- .../server/src/utils/web3.spec.ts | 149 +++++++---- .../server/test/constants.ts | 4 +- .../server/test/fixtures/web3.ts | 31 +++ .../server/test/mock-creators/web3.ts | 9 + 52 files changed, 707 insertions(+), 963 deletions(-) delete mode 100644 packages/apps/reputation-oracle/server/src/common/constants/networks.ts delete mode 100644 packages/apps/reputation-oracle/server/src/common/enums/role.ts delete mode 100644 packages/apps/reputation-oracle/server/src/config/network-config.service.ts delete mode 100644 packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.spec.ts delete mode 100644 packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.spec.ts delete mode 100644 packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts create mode 100644 packages/apps/reputation-oracle/server/test/fixtures/web3.ts create mode 100644 packages/apps/reputation-oracle/server/test/mock-creators/web3.ts diff --git a/packages/apps/reputation-oracle/server/src/app.module.ts b/packages/apps/reputation-oracle/server/src/app.module.ts index 64f1d360cd..b36959d953 100644 --- a/packages/apps/reputation-oracle/server/src/app.module.ts +++ b/packages/apps/reputation-oracle/server/src/app.module.ts @@ -22,7 +22,9 @@ import { QualificationModule } from './modules/qualification/qualification.modul import { EscrowCompletionModule } from './modules/escrow-completion/escrow-completion.module'; import { WebhookIncomingModule } from './modules/webhook/webhook-incoming.module'; import { WebhookOutgoingModule } from './modules/webhook/webhook-outgoing.module'; +import { UserModule } from './modules/user/user.module'; import { EmailModule } from './modules/email/module'; +import { StorageModule } from './modules/storage/storage.module'; import Environment from './utils/environment'; @Module({ @@ -42,6 +44,13 @@ import Environment from './utils/environment'; ], imports: [ ScheduleModule.forRoot(), + ServeStaticModule.forRoot({ + rootPath: join( + __dirname, + '../../../../../../', + 'node_modules/swagger-ui-dist', + ), + }), ConfigModule.forRoot({ /** * First value found takes precendece @@ -49,28 +58,23 @@ import Environment from './utils/environment'; envFilePath: [`.env.${Environment.name}`, '.env'], validationSchema: envValidator, }), + EnvConfigModule, DatabaseModule, - HealthModule, - ReputationModule, - WebhookIncomingModule, - WebhookOutgoingModule, - Web3Module, + HCaptchaModule, AuthModule, - KycModule, - ServeStaticModule.forRoot({ - rootPath: join( - __dirname, - '../../../../../../', - 'node_modules/swagger-ui-dist', - ), - }), CronJobModule, + EmailModule, + EscrowCompletionModule, + HealthModule, + KycModule, PayoutModule, - EnvConfigModule, - HCaptchaModule, QualificationModule, - EscrowCompletionModule, - EmailModule, + ReputationModule, + StorageModule, + UserModule, + Web3Module, + WebhookIncomingModule, + WebhookOutgoingModule, ], }) export class AppModule {} diff --git a/packages/apps/reputation-oracle/server/src/common/constants/networks.ts b/packages/apps/reputation-oracle/server/src/common/constants/networks.ts deleted file mode 100644 index 9868bed377..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/constants/networks.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChainId } from '@human-protocol/sdk'; - -export const TESTNET_CHAIN_IDS = [ - ChainId.BSC_TESTNET, - ChainId.POLYGON_AMOY, - ChainId.SEPOLIA, -]; - -export const MAINNET_CHAIN_IDS = [ChainId.BSC_MAINNET, ChainId.POLYGON]; - -export const LOCALHOST_CHAIN_IDS = [ChainId.LOCALHOST]; diff --git a/packages/apps/reputation-oracle/server/src/common/enums/role.ts b/packages/apps/reputation-oracle/server/src/common/enums/role.ts deleted file mode 100644 index 4460fcfb2d..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/enums/role.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum AuthSignatureRole { - JobLauncher = 'job_launcher', - Exchange = 'exchange', - Recording = 'recording', -} diff --git a/packages/apps/reputation-oracle/server/src/common/enums/web3.ts b/packages/apps/reputation-oracle/server/src/common/enums/web3.ts index d99650b26e..87eb88f9db 100644 --- a/packages/apps/reputation-oracle/server/src/common/enums/web3.ts +++ b/packages/apps/reputation-oracle/server/src/common/enums/web3.ts @@ -1,9 +1,3 @@ -export enum Web3Env { - TESTNET = 'testnet', - MAINNET = 'mainnet', - LOCALHOST = 'localhost', -} - export enum SignatureType { SIGNUP = 'signup', SIGNIN = 'signin', diff --git a/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts b/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts index d25e3c257c..03b304aaf9 100644 --- a/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts +++ b/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts @@ -1,11 +1,3 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common'; -import { SignatureAuthGuard } from './signature.auth'; -import { signMessage } from '../../utils/web3'; -import { ChainId, EscrowUtils } from '@human-protocol/sdk'; -import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from '../../../test/constants'; -import { AuthSignatureRole } from '../enums/role'; - jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), EscrowUtils: { @@ -13,92 +5,200 @@ jest.mock('@human-protocol/sdk', () => ({ }, })); -describe('SignatureAuthGuard', () => { - let guard: SignatureAuthGuard; +import { EscrowUtils } from '@human-protocol/sdk'; +import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common'; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: SignatureAuthGuard, - useValue: new SignatureAuthGuard([ - AuthSignatureRole.JobLauncher, - AuthSignatureRole.Exchange, - AuthSignatureRole.Recording, - ]), - }, - ], - }).compile(); - - guard = module.get(SignatureAuthGuard); - EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({ - launcher: MOCK_ADDRESS, - exchangeOracle: MOCK_ADDRESS, - reputationOracle: MOCK_ADDRESS, - }); - }); +import { + generateContractAddress, + generateEthWallet, + generateTestnetChainId, +} from '../../../test/fixtures/web3'; +import { + createExecutionContextMock, + ExecutionContextMock, +} from '../../../test/mock-creators/nest'; +import { signMessage } from '../../utils/web3'; + +import { AuthSignatureRole, SignatureAuthGuard } from './signature.auth'; - it('should be defined', () => { - expect(guard).toBeDefined(); +describe('SignatureAuthGuard', () => { + it('should throw if empty roles provided in constructor', async () => { + let thrownError; + try { + new SignatureAuthGuard([]); + } catch (error) { + thrownError = error; + } + expect(thrownError).toBeInstanceOf(Error); + expect(thrownError.message).toBe( + 'At least one auth signature role should be provided', + ); }); describe('canActivate', () => { - let context: ExecutionContext; - let mockRequest: any; + let executionContextMock: ExecutionContextMock; + let body: { + chain_id: number; + escrow_address: string; + }; beforeEach(() => { - mockRequest = { - switchToHttp: jest.fn().mockReturnThis(), - getRequest: jest.fn().mockReturnThis(), - headers: {}, - body: {}, - originalUrl: '', + executionContextMock = createExecutionContextMock(); + body = { + chain_id: generateTestnetChainId(), + escrow_address: generateContractAddress(), }; - context = { - switchToHttp: jest.fn().mockReturnThis(), - getRequest: jest.fn(() => mockRequest), - } as any as ExecutionContext; }); - it('should return true if signature is verified', async () => { - const body = { - escrow_address: MOCK_ADDRESS, - chain_id: ChainId.LOCALHOST, - }; - const signature = await signMessage(body, MOCK_PRIVATE_KEY); - mockRequest.headers['human-signature'] = signature; - mockRequest.body = body; - const result = await guard.canActivate(context as any); - expect(result).toBeTruthy(); - expect(EscrowUtils.getEscrow).toHaveBeenCalledWith( - ChainId.LOCALHOST, - MOCK_ADDRESS, + it.each([ + { + name: 'launcher', + role: AuthSignatureRole.JOB_LAUNCHER, + }, + { + name: 'exchangeOracle', + role: AuthSignatureRole.EXCHANGE_ORACLE, + }, + { + name: 'recordingOracle', + role: AuthSignatureRole.RECORDING_ORACLE, + }, + ])( + 'should return true if signature is verified for "$role" role', + async ({ name, role }) => { + const guard = new SignatureAuthGuard([role]); + + const { privateKey, address } = generateEthWallet(); + EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({ + [name]: address, + }); + + const signature = await signMessage(body, privateKey); + + const request = { + headers: { + 'human-signature': signature, + }, + body, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + const result = await guard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + + expect(result).toBeTruthy(); + expect(EscrowUtils.getEscrow).toHaveBeenCalledWith( + body.chain_id, + body.escrow_address, + ); + }, + ); + + it('should throw unauthorized exception if signature is not verified', async () => { + const guard = new SignatureAuthGuard([AuthSignatureRole.JOB_LAUNCHER]); + + const { privateKey, address } = generateEthWallet(); + EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({ + launcher: address, + }); + const signature = await signMessage( + { + 'same-signer': 'different-message', + }, + privateKey, ); + + const request = { + headers: { + 'human-signature': signature, + }, + body, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + let thrownError; + try { + await guard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + } catch (error) { + thrownError = error; + } + expect(thrownError).toBeInstanceOf(HttpException); + expect(thrownError.message).toBe('Invalid web3 signature'); + expect(thrownError.status).toBe(HttpStatus.UNAUTHORIZED); }); - it('should throw unauthorized exception if signature is not verified', async () => { - let catchedError; + it('should throw unauthorized exception if not initialized for signer role', async () => { + const guard = new SignatureAuthGuard([ + AuthSignatureRole.RECORDING_ORACLE, + ]); + + const { privateKey, address } = generateEthWallet(); + EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({ + launcher: address, + exchangeOracle: address, + }); + + const signature = await signMessage(body, privateKey); + + const request = { + headers: { + 'human-signature': signature, + }, + body, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + let thrownError; try { - await guard.canActivate(context as any); + await guard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); } catch (error) { - catchedError = error; + thrownError = error; } - expect(catchedError).toBeInstanceOf(HttpException); - expect(catchedError).toHaveProperty('message', 'Invalid web3 signature'); - expect(catchedError).toHaveProperty('status', HttpStatus.UNAUTHORIZED); + expect(thrownError).toBeInstanceOf(HttpException); + expect(thrownError.message).toBe('Invalid web3 signature'); + expect(thrownError.status).toBe(HttpStatus.UNAUTHORIZED); }); - it('should throw unauthorized exception for unrecognized oracle type', async () => { - mockRequest.originalUrl = '/some/random/path'; - let catchedError; + it('should throw unauthorized exception if escrow lacks oracle addresses', async () => { + const guard = new SignatureAuthGuard([ + AuthSignatureRole.JOB_LAUNCHER, + AuthSignatureRole.EXCHANGE_ORACLE, + AuthSignatureRole.RECORDING_ORACLE, + ]); + + const { privateKey } = generateEthWallet(); + EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({ + launcher: '', + exchangeOracle: '', + recordingOracle: '', + }); + + const signature = await signMessage(body, privateKey); + + const request = { + headers: { + 'human-signature': signature, + }, + body, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + let thrownError; try { - await guard.canActivate(context as any); + await guard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); } catch (error) { - catchedError = error; + thrownError = error; } - expect(catchedError).toBeInstanceOf(HttpException); - expect(catchedError).toHaveProperty('message', 'Invalid web3 signature'); - expect(catchedError).toHaveProperty('status', HttpStatus.UNAUTHORIZED); + expect(thrownError).toBeInstanceOf(HttpException); + expect(thrownError.message).toBe('Invalid web3 signature'); + expect(thrownError.status).toBe(HttpStatus.UNAUTHORIZED); }); }); }); diff --git a/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.ts b/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.ts index 314e3660ff..cace3f0788 100644 --- a/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.ts +++ b/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.ts @@ -1,3 +1,4 @@ +import { EscrowUtils } from '@human-protocol/sdk'; import { CanActivate, ExecutionContext, @@ -7,12 +8,24 @@ import { } from '@nestjs/common'; import { verifySignature } from '../../utils/web3'; import { HEADER_SIGNATURE_KEY } from '../constants'; -import { EscrowUtils } from '@human-protocol/sdk'; -import { AuthSignatureRole } from '../enums/role'; + +export enum AuthSignatureRole { + JOB_LAUNCHER = 'job_launcher', + EXCHANGE_ORACLE = 'exchange', + RECORDING_ORACLE = 'recording', +} @Injectable() export class SignatureAuthGuard implements CanActivate { - constructor(private role: AuthSignatureRole[]) {} + private readonly authorizedSignerRoles: AuthSignatureRole[]; + + constructor(roles: AuthSignatureRole[]) { + if (roles.length === 0) { + throw new Error('At least one auth signature role should be provided'); + } + + this.authorizedSignerRoles = roles; + } public async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); @@ -25,20 +38,23 @@ export class SignatureAuthGuard implements CanActivate { data.escrow_address, ); if ( - this.role.includes(AuthSignatureRole.JobLauncher) && + this.authorizedSignerRoles.includes(AuthSignatureRole.JOB_LAUNCHER) && escrowData.launcher.length - ) + ) { oracleAdresses.push(escrowData.launcher); + } if ( - this.role.includes(AuthSignatureRole.Exchange) && + this.authorizedSignerRoles.includes(AuthSignatureRole.EXCHANGE_ORACLE) && escrowData.exchangeOracle?.length - ) + ) { oracleAdresses.push(escrowData.exchangeOracle); + } if ( - this.role.includes(AuthSignatureRole.Recording) && + this.authorizedSignerRoles.includes(AuthSignatureRole.RECORDING_ORACLE) && escrowData.recordingOracle?.length - ) + ) { oracleAdresses.push(escrowData.recordingOracle); + } const isVerified = verifySignature(data, signature, oracleAdresses); diff --git a/packages/apps/reputation-oracle/server/src/config/config.module.ts b/packages/apps/reputation-oracle/server/src/config/config.module.ts index dcbfed9515..ed148bc9f8 100644 --- a/packages/apps/reputation-oracle/server/src/config/config.module.ts +++ b/packages/apps/reputation-oracle/server/src/config/config.module.ts @@ -1,5 +1,5 @@ import { Module, Global } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { AuthConfigService } from './auth-config.service'; import { ServerConfigService } from './server-config.service'; @@ -10,13 +10,12 @@ import { EmailConfigService } from './email-config.service'; import { Web3ConfigService } from './web3-config.service'; import { ReputationConfigService } from './reputation-config.service'; import { KycConfigService } from './kyc-config.service'; -import { NetworkConfigService } from './network-config.service'; import { HCaptchaConfigService } from './hcaptcha-config.service'; @Global() @Module({ + imports: [ConfigModule], providers: [ - ConfigService, ServerConfigService, AuthConfigService, DatabaseConfigService, @@ -26,11 +25,9 @@ import { HCaptchaConfigService } from './hcaptcha-config.service'; EmailConfigService, KycConfigService, PGPConfigService, - NetworkConfigService, HCaptchaConfigService, ], exports: [ - ConfigService, ServerConfigService, AuthConfigService, DatabaseConfigService, @@ -40,7 +37,6 @@ import { HCaptchaConfigService } from './hcaptcha-config.service'; EmailConfigService, KycConfigService, PGPConfigService, - NetworkConfigService, HCaptchaConfigService, ], }) diff --git a/packages/apps/reputation-oracle/server/src/config/env-schema.ts b/packages/apps/reputation-oracle/server/src/config/env-schema.ts index 763c16b5ca..5f08e479f4 100644 --- a/packages/apps/reputation-oracle/server/src/config/env-schema.ts +++ b/packages/apps/reputation-oracle/server/src/config/env-schema.ts @@ -1,4 +1,5 @@ import * as Joi from 'joi'; +import { Web3Network } from './web3-config.service'; export const envValidator = Joi.object({ // General @@ -34,7 +35,7 @@ export const envValidator = Joi.object({ POSTGRES_URL: Joi.string(), POSTGRES_LOGGING: Joi.string(), // Web3 - WEB3_ENV: Joi.string(), + WEB3_ENV: Joi.string().valid(...Object.values(Web3Network)), WEB3_PRIVATE_KEY: Joi.string().required(), GAS_PRICE_MULTIPLIER: Joi.number(), RPC_URL_SEPOLIA: Joi.string(), diff --git a/packages/apps/reputation-oracle/server/src/config/network-config.service.ts b/packages/apps/reputation-oracle/server/src/config/network-config.service.ts deleted file mode 100644 index c318e548ee..0000000000 --- a/packages/apps/reputation-oracle/server/src/config/network-config.service.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { ChainId } from '@human-protocol/sdk'; -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { Web3Env } from '../common/enums/web3'; -import { - LOCALHOST_CHAIN_IDS, - MAINNET_CHAIN_IDS, - TESTNET_CHAIN_IDS, -} from '../common/constants/networks'; - -export interface TokensList { - [key: string]: string | undefined; -} -export interface NetworkDto { - chainId: number; - rpcUrl?: string; -} - -interface NetworkMapDto { - [key: string]: NetworkDto; -} - -@Injectable() -export class NetworkConfigService { - private readonly networkMap: NetworkMapDto; - - constructor(private configService: ConfigService) { - this.networkMap = { - ...(this.configService.get('RPC_URL_SEPOLIA') && { - sepolia: { - chainId: ChainId.SEPOLIA, - /** - * The RPC URL for the Sepolia network. - */ - rpcUrl: this.configService.get('RPC_URL_SEPOLIA'), - }, - }), - ...(this.configService.get('RPC_URL_POLYGON') && { - polygon: { - chainId: ChainId.POLYGON, - /** - * The RPC URL for the Polygon network. - */ - rpcUrl: this.configService.get('RPC_URL_POLYGON'), - }, - }), - ...(this.configService.get('RPC_URL_POLYGON_AMOY') && { - amoy: { - chainId: ChainId.POLYGON_AMOY, - /** - * The RPC URL for the Polygon Amoy network. - */ - rpcUrl: this.configService.get('RPC_URL_POLYGON_AMOY'), - }, - }), - ...(this.configService.get('RPC_URL_BSC_MAINNET') && { - bsc: { - chainId: ChainId.BSC_MAINNET, - /** - * The RPC URL for the BSC Mainnet network. - */ - rpcUrl: this.configService.get('RPC_URL_BSC_MAINNET'), - }, - }), - ...(this.configService.get('RPC_URL_BSC_TESTNET') && { - bsctest: { - chainId: ChainId.BSC_TESTNET, - /** - * The RPC URL for the BSC Testnet network. - */ - rpcUrl: this.configService.get('RPC_URL_BSC_TESTNET'), - }, - }), - ...(this.configService.get('RPC_URL_LOCALHOST') && { - localhost: { - chainId: ChainId.LOCALHOST, - /** - * The RPC URL for the Localhost network. - */ - rpcUrl: this.configService.get('RPC_URL_LOCALHOST'), - }, - }), - }; - - const validChainIds = (() => { - switch (this.configService.get('WEB3_ENV')) { - case Web3Env.MAINNET: - return MAINNET_CHAIN_IDS; - case Web3Env.LOCALHOST: - return LOCALHOST_CHAIN_IDS; - default: - return TESTNET_CHAIN_IDS; - } - })(); - - this.networkMap = Object.keys(this.networkMap) - .filter((network) => { - const networkConfig = this.networkMap[network]; - return ( - networkConfig.rpcUrl && validChainIds.includes(networkConfig.chainId) - ); - }) - .reduce((newNetworkMap: NetworkMapDto, network) => { - newNetworkMap[network] = this.networkMap[network]; - return newNetworkMap; - }, {}); - } - - get networks(): NetworkDto[] { - return Object.values(this.networkMap).map((network) => network); - } -} diff --git a/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts b/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts index 18d5678bf8..a550796c47 100644 --- a/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts @@ -1,16 +1,29 @@ +import { ChainId } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { Wallet } from 'ethers'; + +export enum Web3Network { + MAINNET = 'mainnet', + TESTNET = 'testnet', + LOCAL = 'localhost', +} @Injectable() export class Web3ConfigService { - constructor(private configService: ConfigService) {} + readonly operatorAddress: string; + + constructor(private configService: ConfigService) { + const wallet = new Wallet(this.privateKey); + this.operatorAddress = wallet.address; + } /** * The environment in which the Web3 application is running. * Default: 'testnet' */ - get env(): string { - return this.configService.get('WEB3_ENV', 'testnet'); + get network(): Web3Network { + return this.configService.get('WEB3_ENV', Web3Network.TESTNET); } /** @@ -21,6 +34,20 @@ export class Web3ConfigService { return this.configService.getOrThrow('WEB3_PRIVATE_KEY'); } + /** + * Returns chain id reputation oracle operates on + */ + get reputationNetworkChainId(): ChainId { + switch (this.network) { + case Web3Network.MAINNET: + return ChainId.POLYGON; + case Web3Network.TESTNET: + return ChainId.POLYGON_AMOY; + case Web3Network.LOCAL: + return ChainId.LOCALHOST; + } + } + /** * Multiplier for gas price adjustments. * Default: 1 @@ -28,4 +55,23 @@ export class Web3ConfigService { get gasPriceMultiplier(): number { return +this.configService.get('GAS_PRICE_MULTIPLIER', 1); } + + getRpcUrlByChainId(chainId: number): string | undefined { + const rpcUrlsByChainId: Record = { + [ChainId.POLYGON]: this.configService.get('RPC_URL_POLYGON'), + [ChainId.POLYGON_AMOY]: this.configService.get( + 'RPC_URL_POLYGON_AMOY', + ), + [ChainId.BSC_MAINNET]: this.configService.get( + 'RPC_URL_BSC_MAINNET', + ), + [ChainId.BSC_TESTNET]: this.configService.get( + 'RPC_URL_BSC_TESTNET', + ), + [ChainId.SEPOLIA]: this.configService.get('RPC_URL_SEPOLIA'), + [ChainId.LOCALHOST]: this.configService.get('RPC_URL_LOCALHOST'), + }; + + return rpcUrlsByChainId[chainId]; + } } diff --git a/packages/apps/reputation-oracle/server/src/database/database.module.ts b/packages/apps/reputation-oracle/server/src/database/database.module.ts index c298adee78..029029a24a 100644 --- a/packages/apps/reputation-oracle/server/src/database/database.module.ts +++ b/packages/apps/reputation-oracle/server/src/database/database.module.ts @@ -1,17 +1,15 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import * as path from 'path'; +import { LoggerOptions } from 'typeorm'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; -import { NS } from '../common/constants'; -import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; +import { NS } from '../common/constants'; import { ReputationEntity } from '../modules/reputation/reputation.entity'; import { TokenEntity } from '../modules/auth/token.entity'; import { UserEntity } from '../modules/user/user.entity'; import { KycEntity } from '../modules/kyc/kyc.entity'; import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; -import { LoggerOptions } from 'typeorm'; import { DatabaseConfigService } from '../config/database-config.service'; import { SiteKeyEntity } from '../modules/user/site-key.entity'; import { QualificationEntity } from '../modules/qualification/qualification.entity'; @@ -22,10 +20,12 @@ import { EscrowCompletionEntity } from '../modules/escrow-completion/escrow-comp import { EscrowPayoutsBatchEntity } from '../modules/escrow-completion/escrow-payouts-batch.entity'; import Environment from '../utils/environment'; +import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; + @Module({ imports: [ TypeOrmModule.forRootAsync({ - imports: [TypeOrmLoggerModule, ConfigModule], + imports: [TypeOrmLoggerModule], inject: [TypeOrmLoggerService, DatabaseConfigService], useFactory: ( typeOrmLoggerService: TypeOrmLoggerService, diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts index e910e81db5..ac9bbd9bf5 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts @@ -1,10 +1,9 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { HCaptchaService } from './hcaptcha.service'; import { HttpModule } from '@nestjs/axios'; @Module({ - imports: [ConfigModule, HttpModule], + imports: [HttpModule], providers: [HCaptchaService], exports: [HCaptchaService], }) diff --git a/packages/apps/reputation-oracle/server/src/main.ts b/packages/apps/reputation-oracle/server/src/main.ts index cf2416f074..1629824e8c 100644 --- a/packages/apps/reputation-oracle/server/src/main.ts +++ b/packages/apps/reputation-oracle/server/src/main.ts @@ -7,7 +7,7 @@ import helmet from 'helmet'; import { AppModule } from './app.module'; import { useContainer } from 'class-validator'; import { ServerConfigService } from './config/server-config.service'; -import { nestLoggerOverride } from './logger'; +import logger, { nestLoggerOverride } from './logger'; async function bootstrap() { const app = await NestFactory.create(AppModule, { @@ -37,7 +37,7 @@ async function bootstrap() { const port = serverConfigService.port; await app.listen(port, host, async () => { - console.info(`API server is running on http://${host}:${port}`); + logger.info(`API server is running on http://${host}:${port}`); }); } diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts index e836375a8b..b0c375f70a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts @@ -40,7 +40,7 @@ import { @ApiTags('Auth') @Controller('/auth') @UseFilters(AuthControllerErrorsFilter) -export class AuthJwtController { +export class AuthController { constructor( private readonly authService: AuthService, private readonly tokenRepository: TokenRepository, diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts index c98a82c4c7..e5bf2d104c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts @@ -1,16 +1,16 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; +import { AuthConfigService } from '../../config/auth-config.service'; +import { HCaptchaModule } from '../../integrations/hcaptcha/hcaptcha.module'; +import { EmailModule } from '../email/module'; import { UserModule } from '../user/user.module'; +import { Web3Module } from '../web3/web3.module'; + import { JwtHttpStrategy } from './strategy'; import { AuthService } from './auth.service'; -import { AuthJwtController } from './auth.controller'; +import { AuthController } from './auth.controller'; import { TokenRepository } from './token.repository'; -import { UserRepository } from '../user/user.repository'; -import { Web3Module } from '../web3/web3.module'; -import { AuthConfigService } from '../../config/auth-config.service'; -import { HCaptchaModule } from '../../integrations/hcaptcha/hcaptcha.module'; -import { EmailModule } from '../email/module'; @Module({ imports: [ @@ -29,8 +29,7 @@ import { EmailModule } from '../email/module'; HCaptchaModule, EmailModule, ], - providers: [JwtHttpStrategy, AuthService, TokenRepository, UserRepository], - controllers: [AuthJwtController], - exports: [AuthService], + providers: [JwtHttpStrategy, AuthService, TokenRepository], + controllers: [AuthController], }) export class AuthModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts index 1fe447ed0d..4ae5bd1448 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts @@ -1,6 +1,5 @@ import { createMock } from '@golevelup/ts-jest'; import { - ChainId, KVStoreClient, KVStoreUtils, Role as SDKRole, @@ -25,7 +24,6 @@ import { } from '../../../test/constants'; import { AuthConfigService } from '../../config/auth-config.service'; import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; -import { NetworkConfigService } from '../../config/network-config.service'; import { ServerConfigService } from '../../config/server-config.service'; import { Web3ConfigService } from '../../config/web3-config.service'; import { JobRequestType } from '../../common/enums'; @@ -74,13 +72,6 @@ jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('mocked-uuid'), })); -jest.spyOn(NetworkConfigService.prototype, 'networks', 'get').mockReturnValue([ - { - chainId: ChainId.POLYGON_AMOY, - rpcUrl: 'https://polygon-amoy.g.alchemy.com/v2/1234567890', - }, -]); - describe('AuthService', () => { let authService: AuthService; let tokenRepository: TokenRepository; @@ -88,7 +79,6 @@ describe('AuthService', () => { let userRepository: UserRepository; let jwtService: JwtService; let emailService: EmailService; - let web3Service: Web3Service; let authConfigService: AuthConfigService; let hcaptchaService: HCaptchaService; @@ -137,10 +127,8 @@ describe('AuthService', () => { provide: Web3Service, useValue: { getSigner: jest.fn().mockReturnValue(signerMock), - getOperatorAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }, }, - NetworkConfigService, ], }).compile(); @@ -150,7 +138,6 @@ describe('AuthService', () => { userRepository = moduleRef.get(UserRepository); jwtService = moduleRef.get(JwtService); emailService = moduleRef.get(EmailService); - web3Service = moduleRef.get(Web3Service); authConfigService = moduleRef.get(AuthConfigService); hcaptchaService = moduleRef.get(HCaptchaService); @@ -301,9 +288,6 @@ describe('AuthService', () => { findTokenMock = jest .spyOn(tokenRepository, 'findOneByUserIdAndType') .mockResolvedValueOnce(null); - jest - .spyOn(web3Service, 'getOperatorAddress') - .mockReturnValueOnce(MOCK_ADDRESS); const result = await authService.auth(userEntity as UserEntity); expect(findTokenMock).toHaveBeenCalledWith( @@ -650,7 +634,7 @@ describe('AuthService', () => { const data = { from: MOCK_ADDRESS.toLowerCase(), - to: web3Service.getOperatorAddress().toLowerCase(), + to: MOCK_ADDRESS.toLowerCase(), contents: 'signin', nonce: nonce, }; diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts index 822f3491e6..72f79a40c4 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts @@ -1,3 +1,9 @@ +import { + KVStoreClient, + KVStoreKeys, + KVStoreUtils, + Role, +} from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @@ -12,14 +18,7 @@ import { TokenEntity, TokenType } from './token.entity'; import { TokenRepository } from './token.repository'; import { verifySignature } from '../../utils/web3'; import { Web3Service } from '../web3/web3.service'; -import { - ChainId, - KVStoreClient, - KVStoreKeys, - KVStoreUtils, - Role, -} from '@human-protocol/sdk'; -import { SignatureType, Web3Env } from '../../common/enums/web3'; +import { SignatureType } from '../../common/enums/web3'; import { prepareSignatureBody } from '../../utils/web3'; import { UserRepository } from '../user/user.repository'; import { AuthConfigService } from '../../config/auth-config.service'; @@ -131,23 +130,15 @@ export class AuthService { TokenType.REFRESH, ); - let chainId: ChainId; - const currentWeb3Env = this.web3ConfigService.env; - if (currentWeb3Env === Web3Env.MAINNET) { - chainId = ChainId.POLYGON; - } else if (currentWeb3Env === Web3Env.LOCALHOST) { - chainId = ChainId.LOCALHOST; - } else { - chainId = ChainId.POLYGON_AMOY; - } + const operatorAddress = this.web3ConfigService.operatorAddress; let status = userEntity.status.toString(); if (userEntity.role === UserRole.OPERATOR && userEntity.evmAddress) { let operatorStatus: string | undefined; try { operatorStatus = await KVStoreUtils.get( - chainId, - this.web3Service.getOperatorAddress(), + this.web3ConfigService.reputationNetworkChainId, + operatorAddress, userEntity.evmAddress.toLowerCase(), ); } catch {} @@ -164,7 +155,7 @@ export class AuthService { wallet_address: userEntity.evmAddress, role: userEntity.role, kyc_status: userEntity.kyc?.status, - reputation_network: this.web3Service.getOperatorAddress(), + reputation_network: operatorAddress, qualifications: userEntity.userQualifications ? userEntity.userQualifications.map( (userQualification) => userQualification.qualification.reference, @@ -307,7 +298,7 @@ export class AuthService { public async web3Signup(data: Web3SignUpDto): Promise { const preSignUpData = prepareSignatureBody({ from: data.address, - to: this.web3Service.getOperatorAddress(), + to: this.web3ConfigService.operatorAddress, contents: SignatureType.SIGNUP, }); @@ -319,15 +310,7 @@ export class AuthService { throw new AuthError(AuthErrorMessage.INVALID_WEB3_SIGNATURE); } - let chainId: ChainId; - const currentWeb3Env = this.web3ConfigService.env; - if (currentWeb3Env === Web3Env.MAINNET) { - chainId = ChainId.POLYGON; - } else if (currentWeb3Env === Web3Env.LOCALHOST) { - chainId = ChainId.LOCALHOST; - } else { - chainId = ChainId.POLYGON_AMOY; - } + const chainId = this.web3ConfigService.reputationNetworkChainId; const signer = this.web3Service.getSigner(chainId); const kvstore = await KVStoreClient.build(signer); @@ -399,7 +382,7 @@ export class AuthService { const preSigninData = prepareSignatureBody({ from: data.address, - to: this.web3Service.getOperatorAddress(), + to: this.web3ConfigService.operatorAddress, contents: SignatureType.SIGNIN, nonce: (await this.userRepository.findOneByAddress(data.address))?.nonce, }); diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts index 8307ac9b72..2459f13b41 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts @@ -1,21 +1,18 @@ -import { Global, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; -import { CronJobService } from './cron-job.service'; -import { CronJobRepository } from './cron-job.repository'; -import { ConfigModule } from '@nestjs/config'; import { EscrowCompletionModule } from '../escrow-completion/escrow-completion.module'; import { WebhookIncomingModule } from '../webhook/webhook-incoming.module'; import { WebhookOutgoingModule } from '../webhook/webhook-outgoing.module'; -@Global() +import { CronJobService } from './cron-job.service'; +import { CronJobRepository } from './cron-job.repository'; + @Module({ imports: [ - ConfigModule, WebhookIncomingModule, WebhookOutgoingModule, EscrowCompletionModule, ], providers: [CronJobService, CronJobRepository], - exports: [CronJobService], }) export class CronJobModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/email/module.ts b/packages/apps/reputation-oracle/server/src/modules/email/module.ts index 193c27a6f3..8df8c3140e 100644 --- a/packages/apps/reputation-oracle/server/src/modules/email/module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/email/module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { MailService } from '@sendgrid/mail'; + import { SendgridEmailService } from './sendgrid.service'; import { EmailService } from './email.service'; @Module({ - imports: [ConfigModule], providers: [ SendgridEmailService, MailService, diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts index bb1728f260..47d0b4a9b5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts @@ -1,21 +1,16 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; -import { EscrowCompletionRepository } from './escrow-completion.repository'; -import { EscrowCompletionService } from './escrow-completion.service'; + import { PayoutModule } from '../payout/payout.module'; import { ReputationModule } from '../reputation/reputation.module'; import { Web3Module } from '../web3/web3.module'; import { WebhookOutgoingModule } from '../webhook/webhook-outgoing.module'; + +import { EscrowCompletionRepository } from './escrow-completion.repository'; +import { EscrowCompletionService } from './escrow-completion.service'; import { EscrowPayoutsBatchRepository } from './escrow-payouts-batch.repository'; @Module({ - imports: [ - ConfigModule, - Web3Module, - PayoutModule, - ReputationModule, - WebhookOutgoingModule, - ], + imports: [Web3Module, PayoutModule, ReputationModule, WebhookOutgoingModule], providers: [ EscrowCompletionService, EscrowCompletionRepository, diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts index 73c8fa9663..c1053af5eb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts @@ -110,7 +110,6 @@ describe('escrowCompletionService', () => { const mockWeb3Service = { getSigner: jest.fn().mockReturnValue(signerMock), calculateGasPrice: jest.fn().mockReturnValue(1000n), - getOperatorAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }; beforeEach(async () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts index af973c28cf..912d7e8a78 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts @@ -3,11 +3,6 @@ import crypto from 'crypto'; import { ethers } from 'ethers'; import stringify from 'json-stable-stringify'; import _ from 'lodash'; -import { Injectable } from '@nestjs/common'; -import { EscrowCompletionStatus, EventType } from '../../common/enums'; -import { ServerConfigService } from '../../config/server-config.service'; -import { EscrowCompletionRepository } from './escrow-completion.repository'; -import { EscrowCompletionEntity } from './escrow-completion.entity'; import { ESCROW_BULK_PAYOUT_MAX_ITEMS, ChainId, @@ -15,12 +10,16 @@ import { EscrowStatus, OperatorUtils, } from '@human-protocol/sdk'; +import { Injectable } from '@nestjs/common'; +import { EscrowCompletionStatus, EventType } from '../../common/enums'; +import { ServerConfigService } from '../../config/server-config.service'; +import { EscrowCompletionRepository } from './escrow-completion.repository'; +import { EscrowCompletionEntity } from './escrow-completion.entity'; import { calculateExponentialBackoffMs } from '../../utils/backoff'; import { BACKOFF_INTERVAL_SECONDS, DEFAULT_BULK_PAYOUT_TX_ID, } from '../../common/constants'; -import { WebhookIncomingService } from '../webhook/webhook-incoming.service'; import { PayoutService } from '../payout/payout.service'; import { ReputationService } from '../reputation/reputation.service'; import { Web3Service } from '../web3/web3.service'; @@ -34,7 +33,7 @@ import logger from '../../logger'; @Injectable() export class EscrowCompletionService { private readonly logger = logger.child({ - context: WebhookIncomingService.name, + context: EscrowCompletionService.name, }); constructor( diff --git a/packages/apps/reputation-oracle/server/src/modules/health/health.module.ts b/packages/apps/reputation-oracle/server/src/modules/health/health.module.ts index 9455cee23b..9ed184e8f6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/health/health.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/health/health.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; -import { ConfigModule } from '@nestjs/config'; import { HealthController } from './health.controller'; @Module({ - imports: [TerminusModule, ConfigModule], + imports: [TerminusModule], controllers: [HealthController], }) export class HealthModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.spec.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.spec.ts deleted file mode 100644 index 9799d287a1..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Test } from '@nestjs/testing'; - -import { KycController } from './kyc.controller'; -import { KycSessionDto } from './kyc.dto'; -import { KycService } from './kyc.service'; -import { EnvConfigModule } from '../../config/config.module'; - -describe('KycController', () => { - let kycController: KycController; - let kycService: KycService; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - providers: [ - { - provide: KycService, - useValue: { - initSession: jest.fn(), - updateKycStatus: jest.fn(), - }, - }, - ], - controllers: [KycController], - imports: [EnvConfigModule], - }).compile(); - - kycService = moduleRef.get(KycService); - kycController = moduleRef.get(KycController); - }); - - describe('startKyc', () => { - it('should call service for authorized user', async () => { - const session: KycSessionDto = { - url: 'https://example.test', - }; - - jest.spyOn(kycService, 'initSession').mockResolvedValueOnce(session); - - expect( - await kycController.startKyc({ - user: { - email: 'test@example.com', - }, - } as any), - ).toEqual(session); - }); - }); - - describe('updateKycStatus', () => { - it('should call service', async () => { - kycService.updateKycStatus = jest.fn(); - - await kycController.updateKycStatus({ - status: 'success', - verification: {}, - technicalData: {}, - } as any); - - expect(kycService.updateKycStatus).toHaveBeenCalledWith({ - status: 'success', - verification: {}, - technicalData: {}, - }); - }); - }); - - describe('updateKycStatus', () => { - it('should call service', async () => { - kycService.getSignedAddress = jest.fn(); - await kycController.getSignedAddress({ - user: { evmAddress: '0x123' }, - } as any); - - expect(kycService.getSignedAddress).toHaveBeenCalledWith({ - evmAddress: '0x123', - }); - }); - }); -}); diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts index 51f6dd5f58..8e1b7693d9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts @@ -1,17 +1,16 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; + +import { UserModule } from '../user/user.module'; +import { Web3Module } from '../web3/web3.module'; import { KycService } from './kyc.service'; import { KycController } from './kyc.controller'; import { KycRepository } from './kyc.repository'; -import { UserModule } from '../user/user.module'; -import { Web3Module } from '../web3/web3.module'; @Module({ - imports: [UserModule, ConfigModule, HttpModule, Web3Module], + imports: [UserModule, HttpModule, Web3Module], providers: [KycService, KycRepository], controllers: [KycController], - exports: [KycService], }) export class KycModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts index 207dfe8f2e..d0fbe26639 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts @@ -1,5 +1,4 @@ import { createMock } from '@golevelup/ts-jest'; -import { ChainId } from '@human-protocol/sdk'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; @@ -7,7 +6,6 @@ import { of } from 'rxjs'; import { DeepPartial } from 'typeorm'; import { MOCK_ADDRESS, mockConfig } from '../../../test/constants'; import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; -import { NetworkConfigService } from '../../config/network-config.service'; import { KycConfigService } from '../../config/kyc-config.service'; import { KycStatus } from '../../common/enums/user'; import { Web3Service } from '../web3/web3.service'; @@ -15,6 +13,7 @@ import { KycEntity } from './kyc.entity'; import { KycRepository } from './kyc.repository'; import { KycService } from './kyc.service'; import { KycError, KycErrorMessage } from './kyc.error'; +import { Web3ConfigService } from '../../config/web3-config.service'; describe('Kyc Service', () => { let kycService: KycService; @@ -58,20 +57,11 @@ describe('Kyc Service', () => { useValue: mockHttpService, }, { provide: KycRepository, useValue: createMock() }, + Web3ConfigService, { provide: Web3Service, useValue: { getSigner: jest.fn().mockReturnValue(signerMock), - getOperatorAddress: jest - .fn() - .mockReturnValue(MOCK_ADDRESS.toLowerCase()), - }, - }, - ConfigService, - { - provide: NetworkConfigService, - useValue: { - networks: [{ chainId: ChainId.LOCALHOST }], }, }, ], @@ -311,7 +301,7 @@ describe('Kyc Service', () => { const result = await kycService.getSignedAddress(mockUserEntity as any); expect(result).toEqual({ - key: `KYC-${MOCK_ADDRESS.toLowerCase()}`, + key: `KYC-${MOCK_ADDRESS}`, value: 'signature', }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts index ee77a6b9a5..be14cfcc22 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts @@ -7,9 +7,9 @@ import { KycRepository } from './kyc.repository'; import { KycStatus } from '../../common/enums/user'; import { firstValueFrom } from 'rxjs'; import { KycConfigService } from '../../config/kyc-config.service'; +import { Web3ConfigService } from '../..//config/web3-config.service'; import { KycEntity } from './kyc.entity'; import { Web3Service } from '../web3/web3.service'; -import { NetworkConfigService } from '../../config/network-config.service'; import { KycErrorMessage, KycError } from './kyc.error'; @@ -20,7 +20,7 @@ export class KycService { private readonly httpService: HttpService, private readonly kycConfigService: KycConfigService, private readonly web3Service: Web3Service, - private readonly networkConfigService: NetworkConfigService, + private readonly web3ConfigService: Web3ConfigService, ) {} public async initSession(userEntity: UserEntity): Promise { @@ -117,11 +117,11 @@ export class KycService { const address = user.evmAddress.toLowerCase(); const signature = await this.web3Service - .getSigner(this.networkConfigService.networks[0].chainId) + .getSigner(this.web3ConfigService.reputationNetworkChainId) .signMessage(address); return { - key: `KYC-${this.web3Service.getOperatorAddress()}`, + key: `KYC-${this.web3ConfigService.operatorAddress}`, value: signature, }; } diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts index 838e554113..c079095406 100644 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts @@ -1,13 +1,13 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; -import { PayoutService } from './payout.service'; import { ReputationModule } from '../reputation/reputation.module'; import { Web3Module } from '../web3/web3.module'; import { StorageModule } from '../storage/storage.module'; +import { PayoutService } from './payout.service'; + @Module({ - imports: [ConfigModule, ReputationModule, Web3Module, StorageModule], + imports: [ReputationModule, Web3Module, StorageModule], providers: [PayoutService], exports: [PayoutService], }) diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.spec.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.spec.ts deleted file mode 100644 index 3a1235ba42..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.spec.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { QualificationController } from './qualification.controller'; -import { QualificationService } from './qualification.service'; -import { CreateQualificationDto, QualificationDto } from './qualification.dto'; - -describe('QualificationController', () => { - let qualificationController: QualificationController; - let qualificationService: QualificationService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [QualificationController], - providers: [ - { - provide: QualificationService, - useValue: { - createQualification: jest.fn(), - getQualifications: jest.fn(), - assign: jest.fn(), - unassign: jest.fn(), - delete: jest.fn(), - }, - }, - ], - }).compile(); - - qualificationController = module.get( - QualificationController, - ); - qualificationService = - module.get(QualificationService); - }); - - describe('create', () => { - it('should create a qualification', async () => { - const createQualificationDto: CreateQualificationDto = { - reference: 'test-ref', - title: 'Test Title', - description: 'Test Description', - expiresAt: new Date().toISOString(), - }; - const result: QualificationDto = { - reference: 'test-ref', - title: 'Test Title', - description: 'Test Description', - expiresAt: createQualificationDto.expiresAt, - }; - - jest - .spyOn(qualificationService, 'createQualification') - .mockResolvedValue(result); - - expect(await qualificationController.create(createQualificationDto)).toBe( - result, - ); - expect(qualificationService.createQualification).toHaveBeenCalledWith( - createQualificationDto, - ); - }); - - it('should create a qualification without expiresAt', async () => { - const createQualificationDto: CreateQualificationDto = { - reference: 'test-ref', - title: 'Test Title', - description: 'Test Description', - }; - const result: QualificationDto = { - reference: 'test-ref', - title: 'Test Title', - description: 'Test Description', - }; - - jest - .spyOn(qualificationService, 'createQualification') - .mockResolvedValue(result); - - expect(await qualificationController.create(createQualificationDto)).toBe( - result, - ); - expect(qualificationService.createQualification).toHaveBeenCalledWith( - createQualificationDto, - ); - }); - }); - - describe('getQualifications', () => { - it('should return an array of qualifications', async () => { - const result: QualificationDto[] = [ - { - reference: 'test-ref', - title: 'Test Title', - description: 'Test Description', - expiresAt: new Date().toISOString(), - }, - ]; - - jest - .spyOn(qualificationService, 'getQualifications') - .mockResolvedValue(result); - - expect(await qualificationController.getQualifications()).toBe(result); - expect(qualificationService.getQualifications).toHaveBeenCalled(); - }); - }); - - describe('assign', () => { - it('should assign a qualification to users', async () => { - const reference = 'test-ref'; - const workerAddresses = ['0x123']; - - jest.spyOn(qualificationService, 'assign').mockResolvedValue(); - - await qualificationController.assign(reference, { workerAddresses }); - expect(qualificationService.assign).toHaveBeenCalledWith( - reference, - workerAddresses, - ); - }); - }); - - describe('unassign', () => { - it('should unassign a qualification from users', async () => { - const reference = 'test-ref'; - const workerAddresses = ['0x123']; - - jest.spyOn(qualificationService, 'unassign').mockResolvedValue(); - - await qualificationController.unassign(reference, { workerAddresses }); - expect(qualificationService.unassign).toHaveBeenCalledWith( - reference, - workerAddresses, - ); - }); - }); - - describe('delete', () => { - it('should delete a qualification', async () => { - const reference = 'test-ref'; - - jest.spyOn(qualificationService, 'delete').mockResolvedValue(); - - await qualificationController.deleteQualification(reference); - expect(qualificationService.delete).toHaveBeenCalledWith(reference); - }); - }); -}); diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.module.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.module.ts index eef848d31c..7f0176383c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.module.ts @@ -1,14 +1,14 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; + +import { UserModule } from '../user/user.module'; + import { QualificationService } from './qualification.service'; import { QualificationRepository } from './qualification.repository'; -import { UserRepository } from '../user/user.repository'; import { QualificationController } from './qualification.controller'; @Module({ - imports: [ConfigModule], - providers: [QualificationService, QualificationRepository, UserRepository], + imports: [UserModule], + providers: [QualificationService, QualificationRepository], controllers: [QualificationController], - exports: [QualificationService], }) export class QualificationModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts deleted file mode 100644 index f9f1e0a1f2..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; - -import { ReputationController } from './reputation.controller'; -import { ReputationService } from './reputation.service'; -import { ReputationRepository } from './reputation.repository'; -import { ReputationLevel } from '../../common/enums'; -import { ReputationDto } from './reputation.dto'; -import { StorageService } from '../storage/storage.service'; -import { Web3Service } from '../web3/web3.service'; -import { MOCK_ADDRESS, mockConfig } from '../../../test/constants'; -import { ReputationConfigService } from '../../config/reputation-config.service'; -import { S3ConfigService } from '../../config/s3-config.service'; -import { PGPConfigService } from '../../config/pgp-config.service'; - -const OPERATOR_ADDRESS = 'TEST_OPERATOR_ADDRESS'; -const CHAIN_ID = 1; - -describe('ReputationController', () => { - let reputationController: ReputationController, - reputationService: ReputationService; - - const signerMock = { - address: MOCK_ADDRESS, - getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), - }; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ConfigModule.forFeature(registerAs('s3', () => mockConfig))], - providers: [ - { - provide: Web3Service, - useValue: { - getSigner: jest.fn().mockReturnValue(signerMock), - }, - }, - StorageService, - ReputationService, - { - provide: ReputationRepository, - useValue: { - find: jest.fn(), - findOne: jest.fn(), - }, - }, - { - provide: ConfigService, - useValue: { - get: jest.fn((key: string) => mockConfig[key]), - getOrThrow: jest.fn((key: string) => { - if (!mockConfig[key]) { - throw new Error(`Configuration key "${key}" does not exist`); - } - return mockConfig[key]; - }), - }, - }, - ReputationConfigService, - S3ConfigService, - PGPConfigService, - ], - }).compile(); - - reputationService = moduleRef.get(ReputationService); - reputationController = new ReputationController(reputationService); - }); - - describe('getReputations', () => { - it('should call service for given chainId', async () => { - const results = [ - { - chainId: CHAIN_ID, - address: OPERATOR_ADDRESS, - reputation: ReputationLevel.LOW, - }, - ]; - - jest - .spyOn(reputationService, 'getReputations') - .mockResolvedValueOnce(results as ReputationDto[]); - - jest - .spyOn(reputationService, 'getReputationLevel') - .mockReturnValueOnce(ReputationLevel.LOW); - - expect( - await reputationController.getReputations({ chainId: CHAIN_ID }), - ).toEqual(results); - }); - }); - - describe('getReputation', () => { - it('should call service', async () => { - const result = { - chainId: CHAIN_ID, - address: OPERATOR_ADDRESS, - reputation: ReputationLevel.LOW, - }; - - jest - .spyOn(reputationService, 'getReputation') - .mockResolvedValueOnce(result as ReputationDto); - - jest - .spyOn(reputationService, 'getReputationLevel') - .mockReturnValueOnce(ReputationLevel.LOW); - - expect( - await reputationController.getReputation( - { - address: OPERATOR_ADDRESS, - }, - { - chainId: CHAIN_ID, - }, - ), - ).toBe(result); - }); - }); -}); diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts index f8ffe20a78..8252728d36 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts @@ -1,15 +1,15 @@ +import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; + +import { StorageModule } from '../storage/storage.module'; +import { Web3Module } from '../web3/web3.module'; import { ReputationService } from './reputation.service'; -import { HttpModule } from '@nestjs/axios'; import { ReputationRepository } from './reputation.repository'; import { ReputationController } from './reputation.controller'; -import { StorageModule } from '../storage/storage.module'; -import { Web3Module } from '../web3/web3.module'; @Module({ - imports: [ConfigModule, HttpModule, StorageModule, Web3Module], + imports: [HttpModule, StorageModule, Web3Module], controllers: [ReputationController], providers: [ReputationService, ReputationRepository], exports: [ReputationService], diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts index 67d371f720..edf9b612d9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts @@ -1,7 +1,9 @@ +import { createMock } from '@golevelup/ts-jest'; +import { ChainId, EscrowClient } from '@human-protocol/sdk'; +import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { ReputationService } from './reputation.service'; import { ReputationRepository } from './reputation.repository'; -import { ChainId } from '@human-protocol/sdk'; import { ReputationEntity } from './reputation.entity'; import { MOCK_ADDRESS, @@ -14,19 +16,17 @@ import { MOCK_S3_USE_SSL, mockConfig, } from '../../../test/constants'; -import { createMock } from '@golevelup/ts-jest'; import { JobRequestType, ReputationEntityType, ReputationLevel, SolutionError, } from '../../common/enums'; -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; import { Web3Service } from '../web3/web3.service'; import { StorageService } from '../storage/storage.service'; -import { EscrowClient } from '@human-protocol/sdk'; import { ReputationConfigService } from '../../config/reputation-config.service'; import { ReputationError, ReputationErrorMessage } from './reputation.error'; +import { Web3ConfigService } from '../../config/web3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -78,11 +78,11 @@ describe('ReputationService', () => { }), }, }, + Web3ConfigService, { provide: Web3Service, useValue: { getSigner: jest.fn().mockReturnValue(signerMock), - getOperatorAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }, }, { provide: StorageService, useValue: createMock() }, diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts index 58b3fb0916..76bec58b85 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts @@ -26,6 +26,7 @@ import { RequestAction } from './reputation.interface'; import { getRequestType } from '../../utils/manifest'; import { CvatManifest } from '../../common/interfaces/manifest'; import { ReputationConfigService } from '../../config/reputation-config.service'; +import { Web3ConfigService } from '../../config/web3-config.service'; import { ReputationEntity } from './reputation.entity'; import { ReputationError, ReputationErrorMessage } from './reputation.error'; @@ -36,6 +37,7 @@ export class ReputationService { private readonly storageService: StorageService, private readonly reputationRepository: ReputationRepository, private readonly reputationConfigService: ReputationConfigService, + private readonly web3ConfigService: Web3ConfigService, private readonly web3Service: Web3Service, ) {} @@ -96,7 +98,7 @@ export class ReputationService { ReputationEntityType.RECORDING_ORACLE, ); - const reputationOracleAddress = this.web3Service.getOperatorAddress(); + const reputationOracleAddress = this.web3ConfigService.operatorAddress; await this.increaseReputation( chainId, reputationOracleAddress, @@ -246,7 +248,7 @@ export class ReputationService { if ( type === ReputationEntityType.REPUTATION_ORACLE && - address === this.web3Service.getOperatorAddress() + address === this.web3ConfigService.operatorAddress ) { reputationEntity.reputationPoints = this.reputationConfigService.highLevel; @@ -290,7 +292,7 @@ export class ReputationService { if ( type === ReputationEntityType.REPUTATION_ORACLE && - address === this.web3Service.getOperatorAddress() + address === this.web3ConfigService.operatorAddress ) { return; } @@ -317,7 +319,7 @@ export class ReputationService { address: string, ): Promise { // https://github.com/humanprotocol/human-protocol/issues/1047 - if (address === this.web3Service.getOperatorAddress()) { + if (address === this.web3ConfigService.operatorAddress) { return { chainId, address, diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts index 70cb4e2341..f11194bc07 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; -import { StorageService } from './storage.service'; + import { Web3Module } from '../web3/web3.module'; +import { StorageService } from './storage.service'; + @Module({ imports: [Web3Module], providers: [StorageService], diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts index df199b95d0..e292483ff1 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts @@ -16,6 +16,13 @@ import { Get, UseFilters, } from '@nestjs/common'; +import { Public } from '../../common/decorators'; +import { JwtAuthGuard } from '../../common/guards'; +import { HCaptchaGuard } from '../../common/guards/hcaptcha'; +import { SignatureType } from '../../common/enums/web3'; +import { RequestWithUser } from '../../common/interfaces/request'; +import { Web3ConfigService } from '../../config/web3-config.service'; +import { prepareSignatureBody } from '../../utils/web3'; import { DisableOperatorDto, PrepareSignatureDto, @@ -27,16 +34,8 @@ import { RegistrationInExchangeOraclesDto, RegistrationInExchangeOracleResponseDto, } from './user.dto'; -import { JwtAuthGuard } from '../../common/guards'; -import { HCaptchaGuard } from '../../common/guards/hcaptcha'; -import { RequestWithUser } from '../../common/interfaces/request'; -import { prepareSignatureBody } from '../../utils/web3'; import { UserService } from './user.service'; -import { Public } from '../../common/decorators'; -import { KycSignedAddressDto } from '../kyc/kyc.dto'; -import { Web3Service } from '../web3/web3.service'; import { UserRepository } from './user.repository'; -import { SignatureType } from '../../common/enums/web3'; import { UserErrorFilter } from './user.error.filter'; /** @@ -54,7 +53,7 @@ import { UserErrorFilter } from './user.error.filter'; export class UserController { constructor( private readonly userService: UserService, - private readonly web3Service: Web3Service, + private readonly web3ConfigService: Web3ConfigService, private readonly userRepository: UserRepository, ) {} @@ -86,7 +85,6 @@ export class UserController { @ApiResponse({ status: 200, description: 'Blockchain address registered successfully', - type: KycSignedAddressDto, }) @ApiResponse({ status: 409, @@ -98,12 +96,8 @@ export class UserController { async registerAddress( @Req() request: RequestWithUser, @Body() data: RegisterAddressRequestDto, - ): Promise { - const registrationResult = await this.userService.registerAddress( - request.user, - data, - ); - return registrationResult; + ): Promise { + await this.userService.registerAddress(request.user, data); } @ApiOperation({ @@ -168,7 +162,7 @@ export class UserController { const preparedSignatureBody = await prepareSignatureBody({ from: data.address, - to: this.web3Service.getOperatorAddress(), + to: this.web3ConfigService.operatorAddress, contents: data.type, nonce, }); diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts index 11db88ff44..c5fbd13bae 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts @@ -1,17 +1,17 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; + +import { HCaptchaModule } from '../../integrations/hcaptcha/hcaptcha.module'; +import { Web3Module } from '../web3/web3.module'; import { UserService } from './user.service'; import { UserRepository } from './user.repository'; import { UserController } from './user.controller'; -import { Web3Module } from '../web3/web3.module'; import { SiteKeyRepository } from './site-key.repository'; -import { HCaptchaModule } from '../../integrations/hcaptcha/hcaptcha.module'; @Module({ - imports: [ConfigModule, Web3Module, HCaptchaModule], + imports: [Web3Module, HCaptchaModule], providers: [UserService, UserRepository, SiteKeyRepository], controllers: [UserController], - exports: [UserService], + exports: [UserService, UserRepository], }) export class UserModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index 96914926c0..ea2e25965c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -34,7 +34,6 @@ import { DuplicatedWalletAddressError, InvalidWeb3SignatureError, } from '../../modules/user/user.error'; -import { NetworkConfigService } from '../../config/network-config.service'; import { SiteKeyType } from '../../common/enums'; jest.mock('@human-protocol/sdk', () => ({ @@ -56,15 +55,6 @@ describe('UserService', () => { let hcaptchaService: HCaptchaService; let siteKeyRepository: SiteKeyRepository; - jest - .spyOn(NetworkConfigService.prototype, 'networks', 'get') - .mockReturnValue([ - { - chainId: ChainId.POLYGON_AMOY, - rpcUrl: 'https://polygon-amoy.g.alchemy.com/v2/1234567890', - }, - ]); - beforeEach(async () => { const signerMock = { address: MOCK_ADDRESS, @@ -88,9 +78,6 @@ describe('UserService', () => { provide: Web3Service, useValue: { getSigner: jest.fn().mockReturnValue(signerMock), - getOperatorAddress: jest - .fn() - .mockReturnValue(MOCK_ADDRESS.toLowerCase()), }, }, { @@ -98,9 +85,14 @@ describe('UserService', () => { useValue: createMock(), }, ConfigService, - Web3ConfigService, + { + provide: Web3ConfigService, + useValue: { + operatorAddress: MOCK_ADDRESS, + reputationNetworkChainId: ChainId.POLYGON_AMOY, + }, + }, HCaptchaConfigService, - NetworkConfigService, ], }).compile(); @@ -374,16 +366,12 @@ describe('UserService', () => { signMessage: jest.fn().mockResolvedValue(signature), }); - const result = await userService.registerAddress( - userEntity as UserEntity, - { address: MOCK_ADDRESS, signature }, - ); + await userService.registerAddress(userEntity as UserEntity, { + address: MOCK_ADDRESS, + signature, + }); expect(userRepository.updateOne).toHaveBeenCalledWith(userEntity); - expect(result).toEqual({ - key: `KYC-${MOCK_ADDRESS.toLowerCase()}`, - value: signature, - }); }); it('should fail if user already have a wallet address', async () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index 8266453c5f..4f64a506a2 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -1,34 +1,35 @@ +import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; +import { ethers } from 'ethers'; +import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; +import { Web3ConfigService } from '../../config/web3-config.service'; import { KycStatus, OperatorStatus, UserStatus, Role, } from '../../common/enums/user'; -import { generateNonce, verifySignature } from '../../utils/web3'; -import { UserEntity } from './user.entity'; -import { RegisterAddressRequestDto } from './user.dto'; -import { UserRepository } from './user.repository'; +import { SignatureType } from '../../common/enums/web3'; +import { SiteKeyType } from '../../common/enums'; +import { HCaptchaService } from '../../integrations/hcaptcha/hcaptcha.service'; +import { + generateNonce, + verifySignature, + prepareSignatureBody, +} from '../../utils/web3'; import { Web3Service } from '../web3/web3.service'; -import { SignatureType, Web3Env } from '../../common/enums/web3'; -import { ChainId, KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; -import { Web3ConfigService } from '../../config/web3-config.service'; import { SiteKeyEntity } from './site-key.entity'; import { SiteKeyRepository } from './site-key.repository'; -import { SiteKeyType } from '../../common/enums'; -import { HCaptchaService } from '../../integrations/hcaptcha/hcaptcha.service'; -import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; -import { NetworkConfigService } from '../../config/network-config.service'; -import { prepareSignatureBody } from '../../utils/web3'; -import { KycSignedAddressDto } from '../kyc/kyc.dto'; -import { ethers } from 'ethers'; +import { RegisterAddressRequestDto } from './user.dto'; +import { UserEntity } from './user.entity'; import { UserError, UserErrorMessage, DuplicatedWalletAddressError, InvalidWeb3SignatureError, } from './user.error'; +import { UserRepository } from './user.repository'; @Injectable() export class UserService { @@ -41,7 +42,6 @@ export class UserService { private readonly hcaptchaService: HCaptchaService, private readonly web3ConfigService: Web3ConfigService, private readonly hcaptchaConfigService: HCaptchaConfigService, - private readonly networkConfigService: NetworkConfigService, ) {} static checkPasswordMatchesHash( @@ -149,7 +149,7 @@ export class UserService { public async registerAddress( user: UserEntity, data: RegisterAddressRequestDto, - ): Promise { + ): Promise { const lowercasedAddress = data.address.toLocaleLowerCase(); if (user.evmAddress) { @@ -169,7 +169,7 @@ export class UserService { // Prepare signed data and verify the signature const signedData = prepareSignatureBody({ from: lowercasedAddress, - to: this.web3Service.getOperatorAddress(), + to: this.web3ConfigService.operatorAddress, contents: SignatureType.REGISTER_ADDRESS, }); const verified = verifySignature(signedData, data.signature, [ @@ -182,15 +182,6 @@ export class UserService { user.evmAddress = lowercasedAddress; await this.userRepository.updateOne(user); - - const signature = await this.web3Service - .getSigner(this.networkConfigService.networks[0].chainId) - .signMessage(lowercasedAddress); - - return { - key: `KYC-${this.web3Service.getOperatorAddress()}`, - value: signature, - }; } public async enableOperator( @@ -199,7 +190,7 @@ export class UserService { ): Promise { const signedData = prepareSignatureBody({ from: user.evmAddress, - to: this.web3Service.getOperatorAddress(), + to: this.web3ConfigService.operatorAddress, contents: SignatureType.ENABLE_OPERATOR, }); @@ -209,16 +200,7 @@ export class UserService { throw new InvalidWeb3SignatureError(user.id, user.evmAddress); } - let chainId: ChainId; - const currentWeb3Env = this.web3ConfigService.env; - if (currentWeb3Env === Web3Env.MAINNET) { - chainId = ChainId.POLYGON; - } else if (currentWeb3Env === Web3Env.LOCALHOST) { - chainId = ChainId.LOCALHOST; - } else { - chainId = ChainId.POLYGON_AMOY; - } - + const chainId = this.web3ConfigService.reputationNetworkChainId; const signer = this.web3Service.getSigner(chainId); const kvstore = await KVStoreClient.build(signer); @@ -240,7 +222,7 @@ export class UserService { ): Promise { const signedData = prepareSignatureBody({ from: user.evmAddress, - to: this.web3Service.getOperatorAddress(), + to: this.web3ConfigService.operatorAddress, contents: SignatureType.DISABLE_OPERATOR, }); @@ -250,18 +232,8 @@ export class UserService { throw new InvalidWeb3SignatureError(user.id, user.evmAddress); } - let chainId: ChainId; - const currentWeb3Env = this.web3ConfigService.env; - if (currentWeb3Env === Web3Env.MAINNET) { - chainId = ChainId.POLYGON; - } else if (currentWeb3Env === Web3Env.LOCALHOST) { - chainId = ChainId.LOCALHOST; - } else { - chainId = ChainId.POLYGON_AMOY; - } - + const chainId = this.web3ConfigService.reputationNetworkChainId; const signer = this.web3Service.getSigner(chainId); - const kvstore = await KVStoreClient.build(signer); const status = await KVStoreUtils.get( diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.module.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.module.ts index e9e7918a93..efaf359f6c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.module.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; + import { Web3Service } from './web3.service'; -import { ConfigModule } from '@nestjs/config'; @Module({ - imports: [ConfigModule], providers: [Web3Service], exports: [Web3Service], }) diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts index b712a5416a..f2f30c31ed 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts @@ -1,64 +1,66 @@ -import { ChainId } from '@human-protocol/sdk'; -import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; +import { FeeData, JsonRpcProvider } from 'ethers'; +import { faker } from '@faker-js/faker'; +import { WalletWithProvider, Web3Service } from './web3.service'; import { - MOCK_ADDRESS, - MOCK_PRIVATE_KEY, - mockConfig, -} from '../../../test/constants'; -import { Web3Service } from './web3.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; -import { NetworkConfigService } from '../../config/network-config.service'; + Web3ConfigService, + Web3Network, +} from '../../config/web3-config.service'; +import { + generateEthWallet, + generateTestnetChainId, +} from '../../../test/fixtures/web3'; +import { createMockProvider } from '../../../test/mock-creators/web3'; -describe('Web3Service', () => { - let web3Service: Web3Service; +const testWallet = generateEthWallet(); - jest - .spyOn(Web3ConfigService.prototype, 'privateKey', 'get') - .mockReturnValue(MOCK_PRIVATE_KEY); +const mockRpcUrl = faker.internet.url(); - jest - .spyOn(NetworkConfigService.prototype, 'networks', 'get') - .mockReturnValue([ - { - chainId: ChainId.POLYGON_AMOY, - rpcUrl: 'https://polygon-amoy.g.alchemy.com/v2/1234567890', - }, - ]); +const mockWeb3ConfigService = { + privateKey: testWallet.privateKey, + operatorAddress: testWallet.address, + network: Web3Network.TESTNET, + gasPriceMultiplier: faker.number.int({ min: 1, max: 42 }), + getRpcUrlByChainId: () => mockRpcUrl, +}; - beforeEach(async () => { +describe('Web3Service', () => { + let web3Service: Web3Service; + + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ providers: [ { - provide: ConfigService, - useValue: { - get: jest.fn((key: string) => mockConfig[key]), - getOrThrow: jest.fn((key: string) => { - if (!mockConfig[key]) { - throw new Error(`Configuration key "${key}" does not exist`); - } - return mockConfig[key]; - }), - }, + provide: Web3ConfigService, + useValue: mockWeb3ConfigService, }, Web3Service, - Web3ConfigService, - NetworkConfigService, ], }).compile(); web3Service = moduleRef.get(Web3Service); }); + it('should succesfully create service instance', () => { + /** + * Constructor throws if configuration is invalid, + * so check for an instance as litmus test + */ + expect(web3Service).toBeDefined(); + }); + describe('getSigner', () => { - it('should return a signer for a valid chainId on TESTNET', () => { - const validChainId = ChainId.POLYGON_AMOY; + it('should return correct signer for a valid chainId on testnet', () => { + const validChainId = generateTestnetChainId(); const signer = web3Service.getSigner(validChainId); expect(signer).toBeDefined(); + expect(signer.address).toEqual(testWallet.address); + expect(signer.privateKey).toEqual(testWallet.privateKey); + expect(signer.provider).toBeInstanceOf(JsonRpcProvider); }); - it('should throw invalid chain id provided for the testnet environment', () => { + it('should throw if invalid chain id provided', () => { const invalidChainId = -42; expect(() => web3Service.getSigner(invalidChainId)).toThrow( @@ -67,10 +69,53 @@ describe('Web3Service', () => { }); }); - describe('getOperatorAddress', () => { - it('should get the operator address', () => { - const operatorAddress = web3Service.getOperatorAddress(); - expect(operatorAddress).toBe(MOCK_ADDRESS); + describe('calculateGasPrice', () => { + const mockProvider = createMockProvider(); + let spyOnGetSigner: jest.SpyInstance; + + beforeAll(() => { + spyOnGetSigner = jest.spyOn(web3Service, 'getSigner').mockImplementation( + () => + ({ + provider: mockProvider, + }) as unknown as WalletWithProvider, + ); + }); + + afterAll(() => { + spyOnGetSigner.mockRestore(); + }); + + afterEach(() => { + mockProvider.getFeeData.mockReset(); + }); + + it('should use multiplier for gas price', async () => { + const testChainId = generateTestnetChainId(); + + const randomGasPrice = faker.number.bigInt({ min: 1n }); + + mockProvider.getFeeData.mockResolvedValueOnce({ + gasPrice: randomGasPrice, + } as FeeData); + + const gasPrice = await web3Service.calculateGasPrice(testChainId); + + const expectedGasPrice = + randomGasPrice * BigInt(mockWeb3ConfigService.gasPriceMultiplier); + expect(gasPrice).toEqual(expectedGasPrice); + }); + + it('should throw if no gas price from provider', async () => { + const testChainId = generateTestnetChainId(); + + mockProvider.getFeeData.mockResolvedValueOnce({ + gasPrice: null, + } as FeeData); + + await expect(web3Service.calculateGasPrice(testChainId)).rejects.toThrow( + `No gas price data for chain id: ${testChainId}`, + ); }); }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts index 9a9b30bcca..4acecc4fec 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts @@ -1,9 +1,27 @@ +import { ChainId } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import { Provider, Wallet, ethers } from 'ethers'; -import { Web3ConfigService } from '../../config/web3-config.service'; -import { NetworkConfigService } from '../../config/network-config.service'; +import { + Web3ConfigService, + Web3Network, +} from '../../config/web3-config.service'; -type WalletWithProvider = Wallet & { provider: Provider }; +const supportedChainIdsByNetwork = { + [Web3Network.MAINNET]: [ChainId.POLYGON, ChainId.BSC_MAINNET], + [Web3Network.TESTNET]: [ + ChainId.POLYGON_AMOY, + ChainId.BSC_TESTNET, + ChainId.SEPOLIA, + ], + [Web3Network.LOCAL]: [ChainId.LOCALHOST], +} as const; + +type Chain = { + id: ChainId; + rpcUrl: string; +}; + +export type WalletWithProvider = Wallet & { provider: Provider }; @Injectable() export class Web3Service { @@ -11,26 +29,55 @@ export class Web3Service { [chainId: number]: WalletWithProvider; } = {}; - constructor( - private readonly web3ConfigService: Web3ConfigService, - private readonly networkConfigService: NetworkConfigService, - ) { + constructor(private readonly web3ConfigService: Web3ConfigService) { const privateKey = this.web3ConfigService.privateKey; - if (!this.networkConfigService.networks.length) { - throw new Error('No networks specified in network config'); - } - - for (const network of this.networkConfigService.networks) { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); - this.signersByChainId[network.chainId] = new Wallet( + for (const chain of this.supportedChains) { + const provider = new ethers.JsonRpcProvider(chain.rpcUrl); + this.signersByChainId[chain.id] = new Wallet( privateKey, provider, ) as WalletWithProvider; } } - public getSigner(chainId: number): WalletWithProvider { + private get supportedChainIds(): ChainId[] { + const configuredNewtork = this.web3ConfigService.network; + + const supportedChainIds = supportedChainIdsByNetwork[configuredNewtork]; + + if (!supportedChainIds) { + throw new Error( + `${configuredNewtork} network is missing chain ids mapping`, + ); + } + + return [...supportedChainIds]; + } + + private get supportedChains(): Chain[] { + const supportedChains: Chain[] = []; + + for (const chainId of this.supportedChainIds) { + const rpcUrl = this.web3ConfigService.getRpcUrlByChainId(chainId); + if (!rpcUrl) { + continue; + } + + supportedChains.push({ + id: chainId, + rpcUrl, + }); + } + + if (!supportedChains.length) { + throw new Error('Supported chains not configured'); + } + + return supportedChains; + } + + getSigner(chainId: number): WalletWithProvider { const signer = this.signersByChainId[chainId]; if (signer) { @@ -40,19 +87,14 @@ export class Web3Service { throw new Error(`No signer for provided chain id: ${chainId}`); } - public async calculateGasPrice(chainId: number): Promise { + async calculateGasPrice(chainId: number): Promise { const signer = this.getSigner(chainId); - const multiplier = this.web3ConfigService.gasPriceMultiplier; - const gasPrice = (await signer.provider.getFeeData()).gasPrice; + const { gasPrice } = await signer.provider.getFeeData(); if (gasPrice) { - return gasPrice * BigInt(multiplier); + return gasPrice * BigInt(this.web3ConfigService.gasPriceMultiplier); } throw new Error(`No gas price data for chain id: ${chainId}`); } - - public getOperatorAddress(): string { - return Object.values(this.signersByChainId)[0].address; - } } diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts index 157786322a..aba8b66063 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts @@ -1,15 +1,14 @@ +import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; -import { HttpModule } from '@nestjs/axios'; -import { Web3Module } from '../web3/web3.module'; +import { EscrowCompletionModule } from '../escrow-completion/escrow-completion.module'; + import { WebhookController } from './webhook.controller'; import { WebhookIncomingRepository } from './webhook-incoming.repository'; -import { EscrowCompletionModule } from '../escrow-completion/escrow-completion.module'; import { WebhookIncomingService } from './webhook-incoming.service'; @Module({ - imports: [ConfigModule, Web3Module, HttpModule, EscrowCompletionModule], + imports: [HttpModule, EscrowCompletionModule], controllers: [WebhookController], providers: [WebhookIncomingService, WebhookIncomingRepository], exports: [WebhookIncomingService], diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts index 146a8faea2..b744a31e1d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts @@ -57,7 +57,6 @@ describe('WebhookIncomingService', () => { // Mock Web3Service const mockWeb3Service = { getSigner: jest.fn().mockReturnValue(signerMock), - getOperatorAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }; beforeEach(async () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts index 3258e2ed03..947e12c163 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts @@ -1,12 +1,13 @@ -import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; + import { Web3Module } from '../web3/web3.module'; + import { WebhookOutgoingRepository } from './webhook-outgoing.repository'; import { WebhookOutgoingService } from './webhook-outgoing.service'; @Module({ - imports: [ConfigModule, Web3Module, HttpModule], + imports: [Web3Module, HttpModule], providers: [WebhookOutgoingService, WebhookOutgoingRepository], exports: [WebhookOutgoingService], }) diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts index c34fa0e2d2..9bee57c77b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts @@ -51,7 +51,6 @@ describe('WebhookOutgoingService', () => { // Mock Web3Service const mockWeb3Service = { getSigner: jest.fn().mockReturnValue(signerMock), - getOperatorAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }; beforeEach(async () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.controller.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.controller.ts index 15db3e012c..a68bf3e65b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.controller.ts @@ -15,10 +15,10 @@ import { ApiBody, } from '@nestjs/swagger'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; -import { SignatureAuthGuard } from '../../common/guards'; import { Public } from '../../common/decorators'; +import { AuthSignatureRole, SignatureAuthGuard } from '../../common/guards'; + import { WebhookIncomingService } from './webhook-incoming.service'; -import { AuthSignatureRole } from '../../common/enums/role'; import { IncomingWebhookDto } from './webhook.dto'; import { IncomingWebhookErrorFilter } from './webhook.error.filter'; @@ -45,7 +45,7 @@ export class WebhookController { status: 202, description: 'Incoming webhook accepted successfully', }) - @UseGuards(new SignatureAuthGuard([AuthSignatureRole.Recording])) + @UseGuards(new SignatureAuthGuard([AuthSignatureRole.RECORDING_ORACLE])) @Post('/') @HttpCode(202) public async createIncomingWebhook( diff --git a/packages/apps/reputation-oracle/server/src/utils/backoff.spec.ts b/packages/apps/reputation-oracle/server/src/utils/backoff.spec.ts index 9b177c72b8..e70f79a219 100644 --- a/packages/apps/reputation-oracle/server/src/utils/backoff.spec.ts +++ b/packages/apps/reputation-oracle/server/src/utils/backoff.spec.ts @@ -1,4 +1,4 @@ -import { faker } from '@faker-js/faker/.'; +import { faker } from '@faker-js/faker'; import { calculateExponentialBackoffMs } from './backoff'; describe('Backoff utilities', () => { diff --git a/packages/apps/reputation-oracle/server/src/utils/web3.spec.ts b/packages/apps/reputation-oracle/server/src/utils/web3.spec.ts index 3eca6770f1..97919e38e1 100644 --- a/packages/apps/reputation-oracle/server/src/utils/web3.spec.ts +++ b/packages/apps/reputation-oracle/server/src/utils/web3.spec.ts @@ -1,95 +1,134 @@ +import { faker } from '@faker-js/faker'; + +import { generateEthWallet } from '../../test/fixtures/web3'; + import { verifySignature, recoverSignerAddress, signMessage } from './web3'; -import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from '../../test/constants'; + +const PERMANENT_PRIVATE_KEY = + '0x85dc77260240f78982bdfdfc0a0cb241a85d2f9833468fae7ec362ec7829ce3a'; +const PERMANENT_ADDRESS = '0x9dfB81606Af98a4776a28Ae0Ae30DA3567ae4B98'; +const PERMANENT_MESSAGE = 'Permanent message to validate exact signature'; +const PERMANENT_SIGNATURE = + '0xf6f61e466793e672bb595f2d86fa9c5bbaadc21f0293e12be86c1f87cdac7d823dec35e40cfecd60e07e933ee256e136c171e42f58eb7b95d9d0bfb7cedf754b1c'; describe('Web3 utilities', () => { - describe('verifySignature', () => { - it('should return true for valid signature', async () => { - const message = 'Hello, this is a signed message!'; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); + let privateKey: string; + let address: string; - const result = verifySignature(message, signature, [MOCK_ADDRESS]); + beforeEach(() => { + ({ privateKey, address } = generateEthWallet()); + }); - expect(result).toBe(true); + describe('signMessage', () => { + const signatureRegex = /^0x[0-9a-fA-F]{130}$/; + + it('should sign message when it is a string', async () => { + const message = faker.lorem.words(); + + const signature = await signMessage(message, privateKey); + + expect(signature).toMatch(signatureRegex); }); - it('should return false for signature not verified', async () => { - const message = 'Hello, this is a signed message!'; + it('should sign message when it is an object', async () => { + const message = { + [faker.string.sample()]: new Date(), + }; - const invalidSignature = await signMessage(message, MOCK_PRIVATE_KEY); - const invalidAddress = '0x1234567890123456789012345678901234567892'; + const signature = await signMessage(message, privateKey); - const result = verifySignature(message, invalidSignature, [ - invalidAddress, - ]); - expect(result).toBe(false); + expect(signature).toMatch(signatureRegex); }); - it('should return false in case of invalid signature', () => { - const message = 'Hello, this is a signed message!'; - const invalidSignature = '0xInvalidSignature'; + it('should return exact signature', async () => { + const signature = await signMessage( + PERMANENT_MESSAGE, + PERMANENT_PRIVATE_KEY, + ); - const result = verifySignature(message, invalidSignature, [MOCK_ADDRESS]); - expect(result).toBe(false); + expect(signature).toBe(PERMANENT_SIGNATURE); }); }); - describe('recoverSignerAddress', () => { - it('should recover the correct signer', async () => { - const message = 'value'; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); - - const result = recoverSignerAddress(message, signature); + describe('verifySignature', () => { + it('should return true for valid exact signature', async () => { + const result = verifySignature(PERMANENT_MESSAGE, PERMANENT_SIGNATURE, [ + PERMANENT_ADDRESS, + ]); - expect(result).toBe(MOCK_ADDRESS); + expect(result).toBe(true); }); - it('should throw conflict exception for invalid signature', () => { - const message = 'Hello, this is a signed message!'; - const invalidSignature = '0xInvalidSignature'; + it('should return true for valid signature', async () => { + const message = faker.lorem.words(); - const signer = recoverSignerAddress(message, invalidSignature); - expect(signer).toBe(null); + const signature = await signMessage(message, privateKey); + + const result = verifySignature(message, signature, [address]); + + expect(result).toBe(true); }); - it('should stringify message object if it is not already a string', async () => { - const message = { key: 'value' }; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); + it('should return false if signature is not valid', async () => { + const message = faker.lorem.words(); + const invalidSignature = '0xInvalidSignature'; - const recoveredAddress = recoverSignerAddress(message, signature); + const result = verifySignature(message, invalidSignature, [address]); - expect(recoveredAddress).toBe(MOCK_ADDRESS); + expect(result).toBe(false); }); - it('should not stringify message if it is already a string', async () => { - const message = 'valid message'; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); + it('should return false when signature not verified', async () => { + const message = faker.lorem.words(); - const recoveredAddress = recoverSignerAddress(message, signature); + const signature = await signMessage(message, privateKey); + + const { address: anotherSignerAddress } = generateEthWallet(); + const result = verifySignature(message, signature, [ + anotherSignerAddress, + ]); - expect(recoveredAddress).toBe(MOCK_ADDRESS); + expect(result).toBe(false); }); }); - describe('signMessage', () => { - it('should return a valid signature', async () => { - const message = 'Hello, this is a test message'; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); + describe('recoverSignerAddress', () => { + it('should recover the exact signer', async () => { + const result = recoverSignerAddress( + PERMANENT_MESSAGE, + PERMANENT_SIGNATURE, + ); - expect(signature).toBeDefined(); + expect(result).toBe(PERMANENT_ADDRESS); }); - it('should stringify message object if it is not already a string', async () => { - const message = { key: 'value' }; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); + it('should recover the correct signer', async () => { + const message = faker.lorem.words(); + const signature = await signMessage(message, privateKey); + + const result = recoverSignerAddress(message, signature); + + expect(result).toBe(address); + }); + + it('should return null for invalid signature', () => { + const message = faker.lorem.words(); + const invalidSignature = '0xInvalidSignature'; + + const signer = recoverSignerAddress(message, invalidSignature); - expect(signature).toBeDefined(); + expect(signer).toBe(null); }); - it('should not stringify message if it is already a string', async () => { - const message = 'valid message'; - const signature = await signMessage(message, MOCK_PRIVATE_KEY); + it('should recover the correct signer if message is an object', async () => { + const message = { + [faker.string.sample()]: new Date(), + }; + const signature = await signMessage(message, privateKey); + + const recoveredAddress = recoverSignerAddress(message, signature); - expect(signature).toBeDefined(); + expect(recoveredAddress).toBe(address); }); }); }); diff --git a/packages/apps/reputation-oracle/server/test/constants.ts b/packages/apps/reputation-oracle/server/test/constants.ts index 552af71a55..c79acac68b 100644 --- a/packages/apps/reputation-oracle/server/test/constants.ts +++ b/packages/apps/reputation-oracle/server/test/constants.ts @@ -1,5 +1,3 @@ -import { Web3Env } from '../src/common/enums/web3'; - export const MOCK_REQUESTER_TITLE = 'Mock job title'; export const MOCK_REQUESTER_DESCRIPTION = 'Mock job description'; export const MOCK_ADDRESS = '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e'; @@ -118,7 +116,7 @@ export const mockConfig: any = { SENDGRID_API_KEY: MOCK_SENDGRID_API_KEY, SENDGRID_FROM_EMAIL: MOCK_SENDGRID_FROM_EMAIL, SENDGRID_FROM_NAME: MOCK_SENDGRID_FROM_NAME, - WEB3_ENV: Web3Env.TESTNET, + WEB3_ENV: 'testnet', QUALIFICATION_MIN_VALIDITY: 1, FE_URL: MOCK_FE_URL, HCAPTCHA_SITE_KEY: MOCK_HCAPTCHA_SITE_KEY, diff --git a/packages/apps/reputation-oracle/server/test/fixtures/web3.ts b/packages/apps/reputation-oracle/server/test/fixtures/web3.ts new file mode 100644 index 0000000000..ec816841c3 --- /dev/null +++ b/packages/apps/reputation-oracle/server/test/fixtures/web3.ts @@ -0,0 +1,31 @@ +import { faker } from '@faker-js/faker'; +import { ChainId } from '@human-protocol/sdk'; +import { ethers } from 'ethers'; + +export const TEST_PRIVATE_KEY = + '0x85dc77260240f78982bdfdfc0a0cb241a85d2f9833468fae7ec362ec7829ce3a'; +export const TEST_ADDRESS = '0x9dfB81606Af98a4776a28Ae0Ae30DA3567ae4B98'; + +export function generateEthWallet() { + const wallet = ethers.Wallet.createRandom(); + + return { + privateKey: wallet.privateKey, + address: wallet.address, + }; +} + +export function generateContractAddress() { + return ethers.getCreateAddress({ + from: generateEthWallet().address, + nonce: faker.number.bigInt(), + }); +} + +export function generateTestnetChainId() { + return faker.helpers.arrayElement([ + ChainId.BSC_TESTNET, + ChainId.POLYGON_AMOY, + ChainId.SEPOLIA, + ]); +} diff --git a/packages/apps/reputation-oracle/server/test/mock-creators/web3.ts b/packages/apps/reputation-oracle/server/test/mock-creators/web3.ts new file mode 100644 index 0000000000..433326a700 --- /dev/null +++ b/packages/apps/reputation-oracle/server/test/mock-creators/web3.ts @@ -0,0 +1,9 @@ +import { Provider } from 'ethers'; + +export function createMockProvider(): jest.Mocked< + Pick +> { + return { + getFeeData: jest.fn(), + }; +} From c046770c7e2de6b9c550e1dadca778d33486569f Mon Sep 17 00:00:00 2001 From: Dmitry Nechay Date: Thu, 27 Feb 2025 14:54:58 +0300 Subject: [PATCH 10/27] fix: add missing env vars to docker setup (#3129) --- packages/apps/human-app/frontend/.env.example | 2 +- scripts/cvat/env-files/.env.human-app-client | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/apps/human-app/frontend/.env.example b/packages/apps/human-app/frontend/.env.example index fbd946ad50..a18d929d1d 100644 --- a/packages/apps/human-app/frontend/.env.example +++ b/packages/apps/human-app/frontend/.env.example @@ -45,7 +45,7 @@ VITE_H_CAPTCHA_ORACLE_ADDRESS= # job types list string VITE_H_CAPTCHA_ORACLE_TASK_TYPES= #string => lists of job types eg.: Image labeling, BBoxes... -# network if network is equl to 'testnet' app will use first tesntet chain from .src/smart-contracts/chains.ts +# if network is equal to 'testnet' app will use first testnet chain from .src/smart-contracts/chains.ts # and first mainnet chain for 'mainnet' VITE_NETWORK= # mainnet|testnet VITE_GOVERNOR_ADDRESS= diff --git a/scripts/cvat/env-files/.env.human-app-client b/scripts/cvat/env-files/.env.human-app-client index 30464f42d5..c70c0f5f49 100644 --- a/scripts/cvat/env-files/.env.human-app-client +++ b/scripts/cvat/env-files/.env.human-app-client @@ -25,4 +25,7 @@ VITE_H_CAPTCHA_ORACLE_ROLE=hcaptcha # Keep it empty VITE_H_CAPTCHA_ORACLE_ADDRESS= VITE_H_CAPTCHA_ORACLE_TASK_TYPES=image_points,image_boxes -VITE_FEATURE_FLAG_JOBS_DISCOVERY=true \ No newline at end of file +VITE_FEATURE_FLAG_JOBS_DISCOVERY=true +# Keep it empty +VITE_GOVERNOR_ADDRESS= +VITE_GOVERNANCE_URL= \ No newline at end of file From 92edc8712bc8ac4fec6ab66317e7771f363f9e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:33:33 +0100 Subject: [PATCH 11/27] Refactor: Remove unused test configurations and dependencies (#3123) * refactor: remove unused test configurations and dependencies from various client packages * refactor: clean up CI workflow and remove unused test files * fix: add missing workspace for staking-dashboard-client in lint script * Update tsconfig.json * Clean up package.json and yarn.lock files --- .github/workflows/ci-test-job-launcher.yaml | 12 - package.json | 4 +- packages/apps/dashboard/client/package.json | 1 - packages/apps/faucet/client/README.md | 6 - packages/apps/faucet/client/package.json | 4 +- packages/apps/faucet/client/tsconfig.json | 4 +- packages/apps/faucet/client/vite.config.ts | 13 +- .../exchange-oracle/client/package.json | 1 - .../exchange-oracle/client/vite.config.ts | 1 - .../transform-enum.interceptor.spec.ts | 30 +-- .../src/modules/webhook/webhook.controller.ts | 4 +- packages/apps/human-app/frontend/package.json | 7 +- .../human-app/frontend/src/setup-tests.ts | 20 -- .../components/ui/modal/modal-header.test.tsx | 48 ---- .../shared/test-utils/render-with-wrapper.tsx | 16 -- .../apps/human-app/frontend/tsconfig.json | 7 +- .../apps/human-app/frontend/vite.config.mjs | 6 - .../transform-enum.interceptor.spec.ts | 30 +-- .../apps/job-launcher/client/package.json | 10 +- .../apps/job-launcher/client/vite.config.ts | 1 - packages/apps/staking/package.json | 1 - packages/apps/staking/tsconfig.json | 20 +- packages/apps/staking/vite.config.ts | 1 - yarn.lock | 249 +----------------- 24 files changed, 55 insertions(+), 441 deletions(-) delete mode 100644 packages/apps/human-app/frontend/src/setup-tests.ts delete mode 100644 packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-header.test.tsx delete mode 100644 packages/apps/human-app/frontend/src/shared/test-utils/render-with-wrapper.tsx diff --git a/.github/workflows/ci-test-job-launcher.yaml b/.github/workflows/ci-test-job-launcher.yaml index b1bdd44d21..0511923a5e 100644 --- a/.github/workflows/ci-test-job-launcher.yaml +++ b/.github/workflows/ci-test-job-launcher.yaml @@ -12,18 +12,6 @@ on: workflow_dispatch: jobs: - job-launcher-client-test: - name: Job Launcher Client Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/job-launcher-client test - name: Run Job Launcher Client test job-launcher-server-test: name: Job Launcher Server Test runs-on: ubuntu-latest diff --git a/package.json b/package.json index 91a1a81845..9afeb2bd79 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "repository": "https://github.com/humanprotocol/human-protocol", "license": "MIT", "scripts": { - "test": "concurrently \"yarn workspace @human-protocol/core test\" \"yarn workspace @human-protocol/sdk test\" \"yarn workspace @human-protocol/subgraph test\" \"yarn workspace @human-protocol/faucet-server test\" \"yarn workspace @human-protocol/job-launcher-server test\" \"yarn workspace @human-protocol/job-launcher-client test\" \"yarn workspace @human-protocol/human-app-frontend test\" \"yarn workspace @human-protocol/human-app-server test\" \"yarn workspace @human-protocol/reputation-oracle test\" \"yarn workspace @human-protocol/fortune-exchange-oracle-server test\" \"yarn workspace @human-protocol/fortune-recording-oracle test\"", - "lint": "concurrently \"yarn workspace @human-protocol/core lint\" \"yarn workspace @human-protocol/sdk lint\" \"yarn workspace @human-protocol/subgraph lint\" \"yarn workspace @human-protocol/faucet-client lint\" \"yarn workspace @human-protocol/faucet-server lint\" \"yarn workspace @human-protocol/job-launcher-server lint\" \"yarn workspace @human-protocol/job-launcher-client lint\" \"yarn workspace @human-protocol/human-app-frontend lint\" \"yarn workspace @human-protocol/human-app-server lint\" \"yarn workspace @human-protocol/reputation-oracle lint\" \"yarn workspace @human-protocol/fortune-exchange-oracle-server lint\" \"yarn workspace @human-protocol/fortune-recording-oracle lint\" \"yarn workspace @human-protocol/dashboard-client lint\"", + "test": "concurrently \"yarn workspace @human-protocol/core test\" \"yarn workspace @human-protocol/sdk test\" \"yarn workspace @human-protocol/subgraph test\" \"yarn workspace @human-protocol/faucet-server test\" \"yarn workspace @human-protocol/job-launcher-server test\" \"yarn workspace @human-protocol/human-app-frontend test\" \"yarn workspace @human-protocol/human-app-server test\" \"yarn workspace @human-protocol/reputation-oracle test\" \"yarn workspace @human-protocol/fortune-exchange-oracle-server test\" \"yarn workspace @human-protocol/fortune-recording-oracle test\"", + "lint": "concurrently \"yarn workspace @human-protocol/core lint\" \"yarn workspace @human-protocol/sdk lint\" \"yarn workspace @human-protocol/subgraph lint\" \"yarn workspace @human-protocol/faucet-client lint\" \"yarn workspace @human-protocol/faucet-server lint\" \"yarn workspace @human-protocol/job-launcher-server lint\" \"yarn workspace @human-protocol/job-launcher-client lint\" \"yarn workspace @human-protocol/human-app-frontend lint\" \"yarn workspace @human-protocol/human-app-server lint\" \"yarn workspace @human-protocol/reputation-oracle lint\" \"yarn workspace @human-protocol/fortune-exchange-oracle-server lint\" \"yarn workspace @human-protocol/fortune-recording-oracle lint\" \"yarn workspace @human-protocol/dashboard-client lint\" \"yarn workspace @human-protocol/staking-dashboard-client lint\"", "prepare": "husky", "postinstall": "yarn workspace @human-protocol/sdk build" }, diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json index 97ff99ed68..363ccb9972 100644 --- a/packages/apps/dashboard/client/package.json +++ b/packages/apps/dashboard/client/package.json @@ -7,7 +7,6 @@ "type": "module", "scripts": { "start": "vite", - "test": "vitest -u", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", diff --git a/packages/apps/faucet/client/README.md b/packages/apps/faucet/client/README.md index 9fd22c9063..db74421b6c 100644 --- a/packages/apps/faucet/client/README.md +++ b/packages/apps/faucet/client/README.md @@ -5,12 +5,6 @@

HUMAN Faucet

The HUMAN Faucet allows users to claim small amounts of HMT tokens to test and explore functionalities within the HUMAN Protocol ecosystem.

-

- - Faucet Check - -

- ## ✨ Demo First, install the dependencies using `yarn` as the package manager: diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json index d1b06fe23e..29e2135a43 100644 --- a/packages/apps/faucet/client/package.json +++ b/packages/apps/faucet/client/package.json @@ -23,8 +23,7 @@ "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^5.1.0", "vite": "^6.2.0", - "vite-plugin-node-polyfills": "^0.22.0", - "vitest": "^1.6.0" + "vite-plugin-node-polyfills": "^0.22.0" }, "scripts": { "lint": "eslint '**/*.{ts,tsx}'", @@ -32,7 +31,6 @@ "build": "vite build", "preview": "vite preview", "start-prod": "serve -s dist", - "test": "vitest -u", "format:prettier": "prettier --write '**/*.{ts,tsx}'", "format:lint": "eslint --fix '**/*.{ts,tsx}'", "format": "yarn format:prettier && yarn format:lint", diff --git a/packages/apps/faucet/client/tsconfig.json b/packages/apps/faucet/client/tsconfig.json index 60010d8450..6555240b7a 100644 --- a/packages/apps/faucet/client/tsconfig.json +++ b/packages/apps/faucet/client/tsconfig.json @@ -18,7 +18,7 @@ "resolveJsonModule": true, "downlevelIteration": true, "baseUrl": "./", - "types": ["node", "jest", "@testing-library/jest-dom"] + "types": ["node"] }, - "include": ["src", "tests"] + "include": ["src"] } diff --git a/packages/apps/faucet/client/vite.config.ts b/packages/apps/faucet/client/vite.config.ts index 7664eb91c1..82dc93a9c0 100644 --- a/packages/apps/faucet/client/vite.config.ts +++ b/packages/apps/faucet/client/vite.config.ts @@ -22,18 +22,7 @@ export default defineConfig(({ mode }) => { plugins: () => react(), }, resolve: { - alias: [ - { find: 'src', replacement: path.resolve(__dirname, 'src') }, - { find: 'tests', replacement: path.resolve(__dirname, 'tests') }, - ], - }, - test: { - globals: true, - environment: 'happy-dom', - setupFiles: './tests/setup.ts', - coverage: { - reporter: ['text', 'json', 'html'], - }, + alias: [{ find: 'src', replacement: path.resolve(__dirname, 'src') }], }, optimizeDeps: { include: ['@human-protocol/sdk'], diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json index 64ab096567..c85013f5ee 100644 --- a/packages/apps/fortune/exchange-oracle/client/package.json +++ b/packages/apps/fortune/exchange-oracle/client/package.json @@ -8,7 +8,6 @@ "build": "vite build", "preview": "vite preview", "start:prod": "serve -s dist", - "test": "vitest run -u", "format:prettier": "prettier --write \"**/*.{ts,tsx,js,jsx}\"", "format:lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"", "format": "yarn format:prettier && yarn format:lint" diff --git a/packages/apps/fortune/exchange-oracle/client/vite.config.ts b/packages/apps/fortune/exchange-oracle/client/vite.config.ts index 10e0db7157..147fdcb76f 100644 --- a/packages/apps/fortune/exchange-oracle/client/vite.config.ts +++ b/packages/apps/fortune/exchange-oracle/client/vite.config.ts @@ -1,4 +1,3 @@ -/// /// import path from 'path'; diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/interceptors/transform-enum.interceptor.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/common/interceptors/transform-enum.interceptor.spec.ts index 9b54afb497..b0420dd949 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/interceptors/transform-enum.interceptor.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/interceptors/transform-enum.interceptor.spec.ts @@ -164,34 +164,34 @@ describe('TransformEnumInterceptor', () => { it('should return bodyOrQuery if instance is not an object', () => { // Test with `null` as the instance - let result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + let result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, null, - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); - + // Test with `undefined` as the instance - result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, undefined, - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); - + // Test with a primitive value (string) as the instance - result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, 'some string', - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); - + // Test with a primitive value (number) as the instance - result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, 123, - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts index ac5d7ee606..8cbbf66e2b 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts @@ -56,9 +56,7 @@ export class WebhookController { status: 404, description: 'Not Found. Could not find the requested content.', }) - public async processWebhook( - @Body() body: WebhookDto, - ): Promise { + public async processWebhook(@Body() body: WebhookDto): Promise { return this.webhookService.handleWebhook(body); } } diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index 81b3e2f3c0..2df5b49bc3 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -8,7 +8,6 @@ "start:prod": "serve -s dist", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", - "test": "vitest", "prepare": "husky" }, "lint-staged": { @@ -54,8 +53,6 @@ "devDependencies": { "@tanstack/eslint-plugin-query": "^5.60.1", "@tanstack/react-query-devtools": "^5.59.16", - "@testing-library/jest-dom": "^6.5.0", - "@testing-library/react": "^15.0.7", "@types/lodash": "^4.17.12", "@types/mui-image": "^1.0.5", "@types/node": "^22.10.5", @@ -64,7 +61,6 @@ "@typescript-eslint/eslint-plugin": "^6.20.0", "@vercel/style-guide": "^6.0.0", "@vitejs/plugin-react": "^4.2.1", - "@vitest/ui": "^2.1.1", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", @@ -74,7 +70,6 @@ "lint-staged": "^15.4.3", "prettier": "^3.4.2", "typescript": "^5.6.3", - "vite": "^6.2.0", - "vitest": "^1.2.2" + "vite": "^6.2.0" } } diff --git a/packages/apps/human-app/frontend/src/setup-tests.ts b/packages/apps/human-app/frontend/src/setup-tests.ts deleted file mode 100644 index cd9d7dda1e..0000000000 --- a/packages/apps/human-app/frontend/src/setup-tests.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as matchers from '@testing-library/jest-dom/matchers'; -import { expect } from 'vitest'; - -vi.mock('zustand'); -//Extends expect function with testing-library matchers -expect.extend(matchers); - -//Mock for the i18 translation https://vitest.dev/api/vi#vi-importactual -vi.mock('react-i18next', async () => { - const mod = await vi.importActual('react-i18next'); - return { - ...mod, - useTranslation: () => { - return { - t: (str: string) => str, - i18n: vi.fn(), - }; - }, - }; -}); diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-header.test.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-header.test.tsx deleted file mode 100644 index af3ca805b7..0000000000 --- a/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-header.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { describe, expect, vi } from 'vitest'; -import { fireEvent } from '@testing-library/react'; -import { renderWithWrapper } from '@/shared/test-utils/render-with-wrapper'; -import { ModalHeader } from '@/shared/components/ui/modal/modal-header'; - -const mockedCloseProcessModal = vi.fn(); - -const closeButtonProps = { - closeButton: { - isVisible: true, - onClick: mockedCloseProcessModal, - }, -}; - -describe('Modal header', () => { - it('is close button visible', () => { - const { getByTestId } = renderWithWrapper( - - ); - const closeButton = getByTestId('button-close-modal'); - expect(closeButton).toBeVisible(); - }); - - it('is close button not visible', () => { - const { queryByTestId } = renderWithWrapper(); - const closeModal = queryByTestId('button-close-modal'); - expect(closeModal).toBeNull(); - }); - - it('is breadcrumb not visible', () => { - const { queryByTestId } = renderWithWrapper(); - const breadcrumb = queryByTestId('breadcrumb-button'); - expect(breadcrumb).toBeNull(); - }); - - it('close modal', () => { - //ARRANGE - const { getByTestId } = renderWithWrapper( - - ); - //ACT - const button = getByTestId('button-close-modal'); - expect(button).toBeVisible(); - //EXPECT - fireEvent.click(button); - expect(mockedCloseProcessModal).toBeCalledTimes(1); - }); -}); diff --git a/packages/apps/human-app/frontend/src/shared/test-utils/render-with-wrapper.tsx b/packages/apps/human-app/frontend/src/shared/test-utils/render-with-wrapper.tsx deleted file mode 100644 index 18ae3669ee..0000000000 --- a/packages/apps/human-app/frontend/src/shared/test-utils/render-with-wrapper.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { RenderOptions } from '@testing-library/react'; -import { render } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import type { ReactElement, ReactNode } from 'react'; - -//custom wrapper from official documentation https://testing-library.com/docs/react-testing-library/setup/ -export function renderWithWrapper( - ui: ReactElement, - options?: Omit -) { - function Wrapper({ children }: { children: ReactNode }) { - return {children}; - } - - return render(ui, { wrapper: Wrapper, ...options }); -} diff --git a/packages/apps/human-app/frontend/tsconfig.json b/packages/apps/human-app/frontend/tsconfig.json index 61f36395e7..030f846e47 100644 --- a/packages/apps/human-app/frontend/tsconfig.json +++ b/packages/apps/human-app/frontend/tsconfig.json @@ -4,12 +4,7 @@ "useDefineForClassFields": true, "module": "Preserve", "lib": ["ES2020", "DOM", "DOM.Iterable"], - "types": [ - "vite-plugin-svgr/client", - "vite/client", - "vitest/globals", - "@testing-library/jest-dom" - ], + "types": ["vite-plugin-svgr/client", "vite/client", "vitest/globals"], "strictNullChecks": true, /* Bundler mode */ diff --git a/packages/apps/human-app/frontend/vite.config.mjs b/packages/apps/human-app/frontend/vite.config.mjs index 1d3bd3b702..847e517a4f 100644 --- a/packages/apps/human-app/frontend/vite.config.mjs +++ b/packages/apps/human-app/frontend/vite.config.mjs @@ -16,12 +16,6 @@ const config = defineConfig({ '@': path.resolve(__dirname, './src'), }, }, - test: { - environment: 'jsdom', - globals: true, - includeSource: ['./src/**/*.{ts,tsx}'], - setupFiles: ['./src/setup-tests.ts/'], - }, build: { target: 'esnext', }, diff --git a/packages/apps/human-app/server/src/common/interceptors/transform-enum.interceptor.spec.ts b/packages/apps/human-app/server/src/common/interceptors/transform-enum.interceptor.spec.ts index ae7a4f0bf5..7063047191 100644 --- a/packages/apps/human-app/server/src/common/interceptors/transform-enum.interceptor.spec.ts +++ b/packages/apps/human-app/server/src/common/interceptors/transform-enum.interceptor.spec.ts @@ -203,34 +203,34 @@ describe('TransformEnumInterceptor', () => { it('should return bodyOrQuery if instance is not an object', () => { // Test with `null` as the instance - let result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + let result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, null, - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); - + // Test with `undefined` as the instance - result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, undefined, - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); - + // Test with a primitive value (string) as the instance - result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, 'some string', - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); - + // Test with a primitive value (number) as the instance - result = interceptor['lowercaseEnumProperties']( - { status: 'PENDING' }, + result = interceptor['lowercaseEnumProperties']( + { status: 'PENDING' }, 123, - MockDto + MockDto, ); expect(result).toEqual({ status: 'PENDING' }); }); diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 352922b3c3..4067b67ccf 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -44,7 +44,6 @@ "build": "vite build", "preview": "vite preview", "start:prod": "serve -s dist", - "test": "vitest run -u", "format:prettier": "prettier --write \"**/*.{ts,tsx,js,jsx}\"", "format:lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"", "format": "yarn format:prettier && yarn format:lint", @@ -63,12 +62,9 @@ ] }, "devDependencies": { - "@testing-library/jest-dom": "^6.5.0", - "@testing-library/react": "^15.0.7", "@types/file-saver": "^2.0.7", "@types/react": "^18.3.12", "@types/react-dom": "^18.2.25", - "@types/react-test-renderer": "^19.0.0", "@types/xml2js": "^0.4.14", "@vitejs/plugin-react": "^4.2.1", "eslint-config-react-app": "^7.0.1", @@ -76,13 +72,9 @@ "eslint-plugin-import": "^2.29.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^5.1.0", - "happy-dom": "^12.9.1", - "identity-obj-proxy": "^3.0.0", - "jsdom": "^25.0.1", "resize-observer-polyfill": "^1.5.1", "vite": "^6.2.0", - "vite-plugin-node-polyfills": "^0.22.0", - "vitest": "^1.6.0" + "vite-plugin-node-polyfills": "^0.22.0" }, "lint-staged": { "*.{ts,tsx}": [ diff --git a/packages/apps/job-launcher/client/vite.config.ts b/packages/apps/job-launcher/client/vite.config.ts index 9849942464..a84bcaaa6b 100644 --- a/packages/apps/job-launcher/client/vite.config.ts +++ b/packages/apps/job-launcher/client/vite.config.ts @@ -24,7 +24,6 @@ export default defineConfig({ test: { globals: true, environment: 'happy-dom', - setupFiles: './tests/setup.ts', coverage: { reporter: ['text', 'json', 'html'], }, diff --git a/packages/apps/staking/package.json b/packages/apps/staking/package.json index c6f6e4b925..ab2da99cb5 100644 --- a/packages/apps/staking/package.json +++ b/packages/apps/staking/package.json @@ -8,7 +8,6 @@ "build": "vite build", "preview": "vite preview", "start:prod": "serve -s dist", - "test": "vitest run -u", "format:prettier": "prettier --write \"**/*.{ts,tsx,js,jsx}\"", "format:lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"", "format": "yarn format:prettier && yarn format:lint", diff --git a/packages/apps/staking/tsconfig.json b/packages/apps/staking/tsconfig.json index a7cca86670..f45117b684 100644 --- a/packages/apps/staking/tsconfig.json +++ b/packages/apps/staking/tsconfig.json @@ -2,12 +2,7 @@ "extends": "../../../tsconfig.json", "compilerOptions": { "target": "es2020", - "lib": [ - "dom", - "dom.iterable", - "esnext", - "es2015.promise" - ], + "lib": ["dom", "dom.iterable", "esnext", "es2015.promise"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -23,14 +18,7 @@ "resolveJsonModule": true, "downlevelIteration": true, "baseUrl": ".", - "types": [ - "node", - "jest", - "@testing-library/jest-dom" - ] + "types": ["node", "jest"] }, - "include": [ - "src", - "tests" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/packages/apps/staking/vite.config.ts b/packages/apps/staking/vite.config.ts index aa0c7cfe2b..546d38f7fe 100644 --- a/packages/apps/staking/vite.config.ts +++ b/packages/apps/staking/vite.config.ts @@ -1,4 +1,3 @@ -/// /// import path from 'path'; diff --git a/yarn.lock b/yarn.lock index 58f8bd750a..9809149e0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@adobe/css-tools@^4.4.0": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.1.tgz#2447a230bfe072c1659e6815129c03cf170710e3" - integrity sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ== - "@adraffy/ens-normalize@1.10.1": version "1.10.1" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" @@ -553,7 +548,7 @@ dependencies: tslib "^2.3.1" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.26.2": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -4730,11 +4725,6 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" -"@polka/url@^1.0.0-next.24": - version "1.0.0-next.28" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" - integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== - "@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" @@ -6171,42 +6161,6 @@ dotenv "^16.4.5" ethers "^6.8.1" -"@testing-library/dom@^10.0.0": - version "10.4.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" - integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^5.0.1" - aria-query "5.3.0" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.5.0" - pretty-format "^27.0.2" - -"@testing-library/jest-dom@^6.5.0": - version "6.6.3" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" - integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== - dependencies: - "@adobe/css-tools" "^4.4.0" - aria-query "^5.0.0" - chalk "^3.0.0" - css.escape "^1.5.1" - dom-accessibility-api "^0.6.3" - lodash "^4.17.21" - redent "^3.0.0" - -"@testing-library/react@^15.0.7": - version "15.0.7" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-15.0.7.tgz#ff733ce0893c875cb5a47672e8e772897128f4ae" - integrity sha512-cg0RvEdD1TIhhkm1IeYMQxrzy0MtUNfa3minv4MjbgcYzJAZ7yD0i0lwoPOTPr+INtiXFezt2o8xMSnyHhEn2Q== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^10.0.0" - "@types/react-dom" "^18.0.0" - "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -6247,11 +6201,6 @@ dependencies: fs-extra "^9.1.0" -"@types/aria-query@^5.0.1": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" - integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== - "@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -6774,7 +6723,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.17", "@types/react-dom@^18.2.22", "@types/react-dom@^18.2.25", "@types/react-dom@^18.3.0": +"@types/react-dom@^18.2.17", "@types/react-dom@^18.2.22", "@types/react-dom@^18.2.25", "@types/react-dom@^18.3.0": version "18.3.5" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716" integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q== @@ -6796,13 +6745,6 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react-test-renderer@^19.0.0": - version "19.0.0" - resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-19.0.0.tgz#4cdeace7561bf359ee167f51704f420c07d4bd8d" - integrity sha512-qDVnNybqFm2eZKJ4jD34EvRd6VHD67KjgnWaEMM0Id9L22EpWe3nOSVKHWL1XWRCxUWe3lhXwlEeCKD1BlJCQA== - dependencies: - "@types/react" "*" - "@types/react-transition-group@^4.4.10", "@types/react-transition-group@^4.4.11": version "4.4.12" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" @@ -7323,13 +7265,6 @@ "@vitest/utils" "1.6.1" chai "^4.3.10" -"@vitest/pretty-format@2.1.9": - version "2.1.9" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz#434ff2f7611689f9ce70cd7d567eceb883653fdf" - integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ== - dependencies: - tinyrainbow "^1.2.0" - "@vitest/runner@1.6.1": version "1.6.1" resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.1.tgz#10f5857c3e376218d58c2bfacfea1161e27e117f" @@ -7355,19 +7290,6 @@ dependencies: tinyspy "^2.2.0" -"@vitest/ui@^2.1.1": - version "2.1.9" - resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-2.1.9.tgz#9e876cf3caf492dd6fddbd7f87b2d6bf7186a7a9" - integrity sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw== - dependencies: - "@vitest/utils" "2.1.9" - fflate "^0.8.2" - flatted "^3.3.1" - pathe "^1.1.2" - sirv "^3.0.0" - tinyglobby "^0.2.10" - tinyrainbow "^1.2.0" - "@vitest/utils@1.6.1": version "1.6.1" resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.1.tgz#6d2f36cb6d866f2bbf59da854a324d6bf8040f17" @@ -7378,15 +7300,6 @@ loupe "^2.3.7" pretty-format "^29.7.0" -"@vitest/utils@2.1.9": - version "2.1.9" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.9.tgz#4f2486de8a54acf7ecbf2c5c24ad7994a680a6c1" - integrity sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ== - dependencies: - "@vitest/pretty-format" "2.1.9" - loupe "^3.1.2" - tinyrainbow "^1.2.0" - "@wagmi/connectors@5.7.3": version "5.7.3" resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-5.7.3.tgz#0e6d274d4734cbfeb8ad964b63b1edcfade42c63" @@ -8234,14 +8147,7 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" - integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== - dependencies: - dequal "^2.0.3" - -aria-query@^5.0.0, aria-query@^5.3.2: +aria-query@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== @@ -9253,14 +9159,6 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^5.0.1, chalk@^5.3.0, chalk@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" @@ -10009,11 +9907,6 @@ css-to-react-native@3.2.0: css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" -css.escape@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== - cssom@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" @@ -10514,16 +10407,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.9: - version "0.5.16" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" - integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== - -dom-accessibility-api@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" - integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== - dom-helpers@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -11881,11 +11764,6 @@ fdir@^6.4.2: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72" integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw== -fflate@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" - integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== - figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -11984,7 +11862,7 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.2.9, flatted@^3.3.1: +flatted@^3.2.9: version "3.3.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== @@ -12681,18 +12559,6 @@ handlebars@^4.0.1: optionalDependencies: uglify-js "^3.1.4" -happy-dom@^12.9.1: - version "12.10.3" - resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-12.10.3.tgz#e61985eff163b822c110458be7f81aa4f94ad588" - integrity sha512-JzUXOh0wdNGY54oKng5hliuBkq/+aT1V3YpTM+lrN/GoLQTANZsMaIvmHiHe612rauHvPJnDZkZ+5GZR++1Abg== - dependencies: - css.escape "^1.5.1" - entities "^4.5.0" - iconv-lite "^0.6.3" - webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - hardhat-abi-exporter@^2.10.1: version "2.10.1" resolved "https://registry.yarnpkg.com/hardhat-abi-exporter/-/hardhat-abi-exporter-2.10.1.tgz#b14884e233c73fe3f43360f014ad7fd6df4b6d25" @@ -12816,11 +12682,6 @@ hardhat@^2.22.18: uuid "^8.3.2" ws "^7.4.6" -harmony-reflect@^1.4.6: - version "1.6.2" - resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" - integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== - has-bigints@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" @@ -13103,7 +12964,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -13115,13 +12976,6 @@ idb-keyval@^6.2.1: resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== -identity-obj-proxy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== - dependencies: - harmony-reflect "^1.4.6" - ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -15032,11 +14886,6 @@ loupe@^2.3.6, loupe@^2.3.7: dependencies: get-func-name "^2.0.1" -loupe@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.3.tgz#042a8f7986d77f3d0f98ef7990a2b2fef18b0fd2" - integrity sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug== - lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -15076,11 +14925,6 @@ luxon@~3.5.0: resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20" integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== -lz-string@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" - integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== - magic-string@0.30.8: version "0.30.8" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" @@ -15566,11 +15410,6 @@ motion@10.16.2: "@motionone/utils" "^10.15.1" "@motionone/vue" "^10.16.2" -mrmime@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" - integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -16486,7 +16325,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathe@^1.1.1, pathe@^1.1.2: +pathe@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== @@ -16882,15 +16721,6 @@ prettier@^3.4.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.0.tgz#50325a28887c6dfdf2ca3f8eaba02b66a8429ca7" integrity sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA== -pretty-format@^27.0.2: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== - dependencies: - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^17.0.1" - pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -17247,11 +17077,6 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -17442,14 +17267,6 @@ recursive-readdir@^2.2.2: dependencies: minimatch "^3.0.5" -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - redis@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/redis/-/redis-4.7.0.tgz#b401787514d25dd0cfc22406d767937ba3be55d6" @@ -18244,15 +18061,6 @@ simplebar-react@^3.2.5: dependencies: simplebar-core "^1.3.0" -sirv@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.0.tgz#f8d90fc528f65dff04cb597a88609d4e8a4361ce" - integrity sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg== - dependencies: - "@polka/url" "^1.0.0-next.24" - mrmime "^2.0.0" - totalist "^3.0.0" - sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -18641,16 +18449,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18759,7 +18558,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18773,13 +18572,6 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -19159,7 +18951,7 @@ tinybench@^2.5.1: resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== -tinyglobby@^0.2.10, tinyglobby@^0.2.6, tinyglobby@^0.2.9: +tinyglobby@^0.2.6, tinyglobby@^0.2.9: version "0.2.10" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f" integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew== @@ -19172,11 +18964,6 @@ tinypool@^0.8.3: resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ== -tinyrainbow@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" - integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== - tinyspy@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" @@ -19240,11 +19027,6 @@ toposort@^2.0.2: resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== -totalist@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" - integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== - tough-cookie@^4.1.2: version "4.1.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" @@ -20092,7 +19874,7 @@ vite@^6.2.0: optionalDependencies: fsevents "~2.3.3" -vitest@^1.2.2, vitest@^1.6.0: +vitest@^1.6.0: version "1.6.1" resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.1.tgz#b4a3097adf8f79ac18bc2e2e0024c534a7a78d2f" integrity sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag== @@ -20703,7 +20485,7 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -20721,15 +20503,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From b78f3a82dc6bb608f421befd4754f0f14dc09891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:19:25 +0100 Subject: [PATCH 12/27] fix: prevent rendering of governance banner if KYC is not approved (#3133) --- .../governance-banner/components/governance-banner.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx index 4c86c51601..ecb939482f 100644 --- a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx +++ b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx @@ -5,11 +5,13 @@ import { useTranslation } from 'react-i18next'; import { env } from '@/shared/env'; import { useColorMode } from '@/shared/contexts/color-mode'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { useWorkerKycStatus } from '@/modules/worker/profile/hooks'; import { useActiveProposalQuery } from '../hooks/use-active-proposal-query'; export function GovernanceBanner() { const { t } = useTranslation(); const { data, isLoading, isError } = useActiveProposalQuery(); + const { kycApproved } = useWorkerKycStatus(); const { colorPalette } = useColorMode(); const [timeRemaining, setTimeRemaining] = useState('00:00:00'); const isMobile = useIsMobile('lg'); @@ -41,7 +43,7 @@ export function GovernanceBanner() { }; }, [data?.deadline]); - if (isLoading || isError || !data) { + if (!kycApproved || isLoading || isError || !data) { return null; } From 5dbf40370e2b33ba10d6defb178a361ba9e3f11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:31:10 +0100 Subject: [PATCH 13/27] refactor: optimisation of authentication wrappers in the router component (#3136) --- .../human-app/frontend/src/router/router.tsx | 101 ++++++++---------- 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/packages/apps/human-app/frontend/src/router/router.tsx b/packages/apps/human-app/frontend/src/router/router.tsx index 8cd33fcf46..685ac94016 100644 --- a/packages/apps/human-app/frontend/src/router/router.tsx +++ b/packages/apps/human-app/frontend/src/router/router.tsx @@ -48,75 +48,66 @@ export function Router() { return ( ( - { - browserAuthProvider.signOut({ - callback: () => { - window.location.reload(); - }, - }); - }} - topMenuItems={workerDrawerTopMenuItems(user)} - /> - )} - renderHCaptchaStatisticsDrawer={(isOpen) => ( - - )} - /> + + ( + { + browserAuthProvider.signOut({ + callback: () => { + window.location.reload(); + }, + }); + }} + topMenuItems={workerDrawerTopMenuItems(user)} + /> + )} + renderHCaptchaStatisticsDrawer={(isOpen) => ( + + )} + /> + } key={routerProps.path} path={routerProps.path} > - - <>{routerProps.element} - - } - path={routerProps.path} - /> + ); })} {web3ProtectedRoutes.map(({ routerProps, pageHeaderProps }) => ( ( - { - browserAuthProvider.signOut({ - callback: () => { - window.location.reload(); - }, - }); - }} + + + ( + { + browserAuthProvider.signOut({ + callback: () => { + window.location.reload(); + }, + }); + }} + /> + )} /> - )} - /> + + } key={routerProps.path} + path={routerProps.path} > - - - <>{routerProps.element} - - - } - path={routerProps.path} - /> + ))} From 8b350763dbcf1968c6287f1566bb3a4425fe7ed2 Mon Sep 17 00:00:00 2001 From: mpblocky <185767042+mpblocky@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:35:36 +0100 Subject: [PATCH 14/27] [HUMAN App] chore: remove unused files (#3135) --- .../hooks/use-register-address-on-chain.ts | 66 ------------------- .../worker/services/get-signed-address.ts | 29 -------- 2 files changed, 95 deletions(-) delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address-on-chain.ts delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/services/get-signed-address.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address-on-chain.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address-on-chain.ts deleted file mode 100644 index 145994de8a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address-on-chain.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { JsonRpcSigner } from 'ethers'; -import { t } from 'i18next'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { ethKvStoreSetBulk } from '@/modules/smart-contracts/EthKVStore/eth-kv-store-set-bulk'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; -import type { SignedAddressSuccess } from '@/modules/worker/services/get-signed-address'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { checkNetwork } from '@/modules/smart-contracts/check-network'; - -async function registerAddressInKVStore({ - key, - value, - signer, - chainId, -}: { - key: string; - value: string; - signer?: JsonRpcSigner; - chainId: number; -}) { - const contractAddress = getContractAddress({ - contractName: 'EthKVStore', - }); - - await ethKvStoreSetBulk({ - keys: [key], - values: [value], - signer, - chainId, - contractAddress, - }); -} - -export function useRegisterAddressOnChainMutation() { - const queryClient = useQueryClient(); - const { user } = useAuthenticatedUser(); - const { web3ProviderMutation, chainId, address } = useWalletConnect(); - - return useMutation({ - mutationFn: async (data: SignedAddressSuccess) => { - const network = await web3ProviderMutation.data?.provider.getNetwork(); - - if (!network) { - throw new Error('No network'); - } - checkNetwork(network); - if (address?.toLowerCase() !== user.wallet_address?.toLowerCase()) { - throw new Error(t('worker.profile.wrongWalletAddress')); - } - - await registerAddressInKVStore({ - ...data, - signer: web3ProviderMutation.data?.signer, - chainId: chainId ?? -1, - }); - }, - onSuccess: async () => { - await queryClient.invalidateQueries(); - }, - onError: async () => { - await queryClient.invalidateQueries(); - }, - mutationKey: [user.wallet_address, address], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/get-signed-address.ts b/packages/apps/human-app/frontend/src/modules/worker/services/get-signed-address.ts deleted file mode 100644 index 85406cb9a0..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/get-signed-address.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { z } from 'zod'; -import { apiClient } from '@/api/api-client'; -import { apiPaths } from '@/api/api-paths'; - -const signedAddressSuccessSchema = z.object({ - key: z.string(), - value: z.string(), -}); - -export type SignedAddressSuccess = z.infer; - -const getSignedAddress = async () => { - return apiClient(apiPaths.worker.signedAddress.path, { - authenticated: true, - withAuthRetry: apiPaths.worker.signedAddress.withAuthRetry, - successSchema: signedAddressSuccessSchema, - options: { - method: 'GET', - }, - }); -}; - -export function useGetSignedAddress() { - return useQuery({ - queryKey: ['getSignedAddress'], - queryFn: getSignedAddress, - }); -} From 1ce053931de2b20b07eb319ea516d6b35ae472f3 Mon Sep 17 00:00:00 2001 From: adrian-oleskiewicz Date: Fri, 28 Feb 2025 14:58:35 +0100 Subject: [PATCH 15/27] [HUMAN App] refactor: email verification pages (#3009) --- .../email-verification-form-container.tsx | 63 ++++++++ .../components/email-verification-process.tsx | 37 +++++ .../email-verification-success-message.tsx | 33 ++++ .../email-verification/components/index.ts | 2 + .../resend-verification-email-form.tsx | 84 ++++++++++ .../worker/email-verification/hooks/index.ts | 4 + .../hooks/use-email-verification-query.ts} | 0 .../hooks/use-email-verification-token.ts | 25 +++ .../hooks/use-resend-email-router-params.ts | 16 ++ .../hooks/use-resend-email.ts | 48 ++++++ .../worker/email-verification/index.ts | 2 + .../worker/email-verification/pages/index.ts | 2 + ...worker-email-verification-process.page.tsx | 19 +++ .../pages/worker-verify-email.page.tsx | 5 + .../email-verification.page.tsx | 85 ---------- .../email-verification/verify-email.page.tsx | 149 ------------------ .../human-app/frontend/src/router/routes.tsx | 12 +- .../frontend/src/shared/i18n/en.json | 8 +- 18 files changed, 353 insertions(+), 241 deletions(-) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/components/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/index.ts rename packages/apps/human-app/frontend/src/modules/worker/{services/email-verification.ts => email-verification/hooks/use-email-verification-query.ts} (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/views/email-verification/email-verification.page.tsx delete mode 100644 packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx new file mode 100644 index 0000000000..d1bac7f655 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx @@ -0,0 +1,63 @@ +import { useNavigate } from 'react-router-dom'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PageCard, PageCardLoader } from '@/shared/components/ui/page-card'; +import { getErrorMessageForError } from '@/shared/errors'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; +import { routerPaths } from '@/router/router-paths'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; +import { useResendEmailRouterParams, useResendEmail } from '../hooks'; +import { ResendVerificationEmailForm } from './resend-verification-email-form'; + +export function EmailVerificationFormContainer() { + const { user, signOut } = useAuth(); + const navigate = useNavigate(); + const routerState = useResendEmailRouterParams(); + const { methods, handleResend, isError, error, isSuccess } = useResendEmail( + routerState?.email ?? '' + ); + const { showNotification } = useNotification(); + const { t } = useTranslation(); + const isAuthenticated = Boolean(user); + + useEffect(() => { + if (isError) { + showNotification({ + message: getErrorMessageForError(error), + type: TopNotificationType.WARNING, + }); + } + }, [isError, error, showNotification]); + + useEffect(() => { + if (isSuccess && methods.formState.isSubmitSuccessful) { + showNotification({ + message: t('worker.sendResetLinkSuccess.successResent'), + type: TopNotificationType.SUCCESS, + }); + } + }, [isSuccess, methods.formState.isSubmitSuccessful, showNotification, t]); + + const handleCancel = () => { + signOut(); + navigate(routerPaths.homePage); + }; + + if (!routerState?.email) { + return ; + } + + return ( + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx new file mode 100644 index 0000000000..bde02c5aed --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx @@ -0,0 +1,37 @@ +import { + PageCardError, + PageCardLoader, +} from '@/shared/components/ui/page-card'; +import { getErrorMessageForError } from '@/shared/errors'; +import { useVerifyEmailQuery } from '../hooks'; +import { EmailVerificationSuccessMessage } from './email-verification-success-message'; + +interface EmailVerificationProcessProps { + token: string; +} + +function useEmailVerification(token: string) { + const { error, isError, isPending } = useVerifyEmailQuery({ token }); + + return { + error, + isError, + isPending, + }; +} + +export function EmailVerificationProcess({ + token, +}: Readonly) { + const { error, isError, isPending } = useEmailVerification(token); + + if (isError) { + return ; + } + + if (isPending) { + return ; + } + + return ; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx new file mode 100644 index 0000000000..1fcd31c0ab --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx @@ -0,0 +1,33 @@ +import { t } from 'i18next'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import { Link } from 'react-router-dom'; +import { Button } from '@/shared/components/ui/button'; +import { PageCard } from '@/shared/components/ui/page-card'; +import { SuccessLabel } from '@/shared/components/ui/success-label'; +import { routerPaths } from '@/router/router-paths'; + +export function EmailVerificationSuccessMessage() { + return ( + {t('worker.emailVerification.title')}} + > + + + {t('worker.emailVerification.description')} + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/index.ts new file mode 100644 index 0000000000..92391bf09c --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/index.ts @@ -0,0 +1,2 @@ +export * from './email-verification-form-container'; +export * from './email-verification-process'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx new file mode 100644 index 0000000000..4c21687f02 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx @@ -0,0 +1,84 @@ +import { FormProvider } from 'react-hook-form'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import { Trans, useTranslation } from 'react-i18next'; +import type { UseFormReturn } from 'react-hook-form'; +import { Button } from '@/shared/components/ui/button'; +import type { ResendEmailVerificationDto } from '@/modules/worker/services/resend-email-verification'; +import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; +import { MailTo } from '@/shared/components/ui/mail-to'; +import { env } from '@/shared/env'; + +interface ResendVerificationEmailFormProps { + methods: UseFormReturn>; + handleResend: ( + data: Pick + ) => void; + email: string; + isAuthenticated: boolean; +} + +export function ResendVerificationEmailForm({ + methods, + handleResend, + email, + isAuthenticated, +}: Readonly) { + const { t } = useTranslation(); + + return ( + +
{ + void methods.handleSubmit(handleResend)(event); + }} + > + + + , + }} + i18nKey="worker.verifyEmail.paragraph1" + values={{ email }} + /> + + + {t('worker.verifyEmail.paragraph2')} + + + , + }} + i18nKey="worker.verifyEmail.paragraph3" + /> + + + + ), + 2: , + }} + i18nKey="worker.verifyEmail.paragraph4" + /> + + {isAuthenticated && ( + <> + + + + )} + +
+
+ ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/index.ts new file mode 100644 index 0000000000..365120a8e1 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/index.ts @@ -0,0 +1,4 @@ +export * from './use-email-verification-query'; +export * from './use-email-verification-token'; +export * from './use-resend-email'; +export * from './use-resend-email-router-params'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/email-verification.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-query.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/email-verification.ts rename to packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-query.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts new file mode 100644 index 0000000000..1e8cc8aa3b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; +import { useLocationState } from '@/modules/worker/hooks/use-location-state'; + +export const tokenSchema = z.string().transform((value, ctx) => { + const token = value.split('=')[1]; + if (!token) { + ctx.addIssue({ + fatal: true, + code: z.ZodIssueCode.custom, + message: 'error', + }); + } + return token; +}); + +export function useEmailVerificationToken() { + const { field: token } = useLocationState({ + schema: tokenSchema, + locationStorage: 'search', + }); + + return { + token, + }; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts new file mode 100644 index 0000000000..6778862e1e --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; +import { useLocationState } from '@/modules/worker/hooks/use-location-state'; + +export const routerStateSchema = z.object({ + email: z.string().email(), + resendOnMount: z.boolean().optional(), +}); + +export function useResendEmailRouterParams() { + const { field: routerState } = useLocationState({ + keyInStorage: 'routerState', + schema: routerStateSchema, + }); + + return routerState; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts new file mode 100644 index 0000000000..9edae9d145 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts @@ -0,0 +1,48 @@ +/* eslint-disable camelcase -- ...*/ +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { + useResendEmailVerificationWorkerMutation, + resendEmailVerificationHcaptchaSchema, +} from '@/modules/worker/services/resend-email-verification'; +import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; +import type { ResendEmailVerificationDto } from '@/modules/worker/services/resend-email-verification'; + +export function useResendEmail(email: string) { + const { + isError, + error, + isSuccess, + mutate: resendEmailVerificationMutation, + reset: resendEmailVerificationMutationReset, + } = useResendEmailVerificationWorkerMutation(); + const methods = useForm>({ + defaultValues: { + h_captcha_token: '', + }, + resolver: zodResolver(resendEmailVerificationHcaptchaSchema), + }); + + useResetMutationErrors(methods.watch, resendEmailVerificationMutationReset); + + const handleResend = ( + data: Pick + ) => { + if (!email) { + return; + } + + resendEmailVerificationMutation({ + email, + h_captcha_token: data.h_captcha_token, + }); + }; + + return { + isError, + isSuccess, + error, + methods, + handleResend, + }; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/index.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/index.ts new file mode 100644 index 0000000000..377fa99bc5 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/index.ts @@ -0,0 +1,2 @@ +export * from './pages/worker-email-verification-process.page'; +export * from './pages/worker-verify-email.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts new file mode 100644 index 0000000000..27efb8dd15 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts @@ -0,0 +1,2 @@ +export * from './worker-email-verification-process.page'; +export * from './worker-verify-email.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx new file mode 100644 index 0000000000..3ee0ee1c05 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx @@ -0,0 +1,19 @@ +import { useTranslation } from 'react-i18next'; +import { PageCardError } from '@/shared/components/ui/page-card'; +import { EmailVerificationProcess } from '../components'; +import { useEmailVerificationToken } from '../hooks'; + +export function WorkerEmailVerificationProcessPage() { + const { t } = useTranslation(); + const { token } = useEmailVerificationToken(); + + if (!token) { + return ( + + ); + } + + return ; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx new file mode 100644 index 0000000000..d27a145f3a --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx @@ -0,0 +1,5 @@ +import { EmailVerificationFormContainer } from '../components'; + +export function WorkerVerifyEmailPage() { + return ; +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/email-verification.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/email-verification.page.tsx deleted file mode 100644 index 6cea27564c..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/email-verification.page.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import Grid from '@mui/material/Grid'; -import { t } from 'i18next'; -import Typography from '@mui/material/Typography'; -import { z } from 'zod'; -import { Link } from 'react-router-dom'; -import { Button } from '@/shared/components/ui/button'; -import { routerPaths } from '@/router/router-paths'; -import { useVerifyEmailQuery } from '@/modules/worker/services/email-verification'; -import { SuccessLabel } from '@/shared/components/ui/success-label'; -import { - PageCardError, - PageCardLoader, - PageCard, -} from '@/shared/components/ui/page-card'; -import { useLocationState } from '@/modules/worker/hooks/use-location-state'; -import { getErrorMessageForError } from '@/shared/errors'; - -const tokenSchema = z.string().transform((value, ctx) => { - const token = value.split('=')[1]; - if (!token) { - ctx.addIssue({ - fatal: true, - code: z.ZodIssueCode.custom, - message: 'error', - }); - } - - return token; -}); - -export function EmailVerificationWorkerPage() { - const { field: token } = useLocationState({ - schema: tokenSchema, - locationStorage: 'search', - }); - - if (token === undefined) { - return ; - } - - return ; -} - -export function EmailVerificationWorker({ token }: { token: string }) { - const { - error: emailVerificationWorkerError, - isError: isEmailVerificationWorkerError, - isPending: isEmailVerificationWorkerPending, - } = useVerifyEmailQuery({ token }); - - if (isEmailVerificationWorkerError) { - return ( - - ); - } - - if (isEmailVerificationWorkerPending) { - return ; - } - - return ( - {t('worker.emailVerification.title')}} - > - - - {t('worker.emailVerification.description')} - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx deleted file mode 100644 index 96167050dc..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/views/email-verification/verify-email.page.tsx +++ /dev/null @@ -1,149 +0,0 @@ -/* eslint-disable camelcase -- ...*/ -import { Grid, Typography } from '@mui/material'; -import { Trans, useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { z } from 'zod'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { FormProvider, useForm } from 'react-hook-form'; -import { PageCard, PageCardLoader } from '@/shared/components/ui/page-card'; -import { useLocationState } from '@/modules/worker/hooks/use-location-state'; -import { env } from '@/shared/env'; -import type { ResendEmailVerificationDto } from '@/modules/worker/services/resend-email-verification'; -import { - resendEmailVerificationHcaptchaSchema, - useResendEmailVerificationWorkerMutation, - useResendEmailVerificationWorkerMutationState, -} from '@/modules/worker/services/resend-email-verification'; -import { Alert } from '@/shared/components/ui/alert'; -import { getErrorMessageForError } from '@/shared/errors'; -import { HCaptchaForm } from '@/shared/components/hcaptcha'; -import { Button } from '@/shared/components/ui/button'; -import { useAuth } from '@/modules/auth/hooks/use-auth'; -import { routerPaths } from '@/router/router-paths'; -import { MailTo } from '@/shared/components/ui/mail-to'; -import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; - -export function VerifyEmailWorkerPage() { - const { colorPalette, isDarkMode } = useColorMode(); - const navigate = useNavigate(); - const { signOut, user } = useAuth(); - const isAuthenticated = Boolean(user); - const { t } = useTranslation(); - const { field: routerState } = useLocationState({ - keyInStorage: 'routerState', - schema: z.object({ - email: z.string().email(), - resendOnMount: z.boolean().optional(), - }), - }); - const mutationState = useResendEmailVerificationWorkerMutationState(); - const { - mutate: resendEmailVerificationMutation, - reset: resendEmailVerificationMutationReset, - } = useResendEmailVerificationWorkerMutation(); - - const methods = useForm>({ - defaultValues: { - h_captcha_token: '', - }, - resolver: zodResolver(resendEmailVerificationHcaptchaSchema), - }); - - useResetMutationErrors(methods.watch, resendEmailVerificationMutationReset); - - const handleResend = ( - data: Pick - ) => { - resendEmailVerificationMutation({ - email: routerState?.email ?? '', - h_captcha_token: data.h_captcha_token, - }); - }; - - if (mutationState?.status === 'pending') { - return ; - } - - return ( - - {getErrorMessageForError(mutationState.error)} - - ) : undefined - } - cancelNavigation={() => { - signOut(); - navigate(routerPaths.homePage); - }} - title={t('worker.verifyEmail.title')} - > - -
{ - void methods.handleSubmit(handleResend)(event); - }} - > - - - , - }} - i18nKey="worker.verifyEmail.paragraph1" - values={{ email: routerState?.email }} - /> - - - {t('worker.verifyEmail.paragraph2')} - - - , - }} - i18nKey="worker.verifyEmail.paragraph3" - /> - - {isAuthenticated ? ( - <> - - - - ) : null} - - - ), - 2: , - }} - i18nKey="worker.verifyEmail.paragraph4" - /> - - -
-
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx index 7f0a411843..0f8b8935af 100644 --- a/packages/apps/human-app/frontend/src/router/routes.tsx +++ b/packages/apps/human-app/frontend/src/router/routes.tsx @@ -17,8 +17,6 @@ import type { PageHeaderProps } from '@/shared/components/layout/protected/page- import { SetUpOperatorPage } from '@/modules/operator/views/sign-up/set-up-operator.page'; import { EditExistingKeysSuccessPage } from '@/modules/operator/views/sign-up/edit-existing-keys-success.page'; import { AddKeysOperatorPage } from '@/modules/operator/views/sign-up/add-keys.page'; -import { VerifyEmailWorkerPage } from '@/modules/worker/views/email-verification/verify-email.page'; -import { EmailVerificationWorkerPage } from '@/modules/worker/views/email-verification/email-verification.page'; import { AddStakeOperatorPage } from '@/modules/operator/views/sign-up/add-stake.page'; import { ConnectWalletOperatorPage } from '@/modules/operator/views/sign-up/connect-wallet.page'; import { OperatorProfilePage } from '@/modules/operator/views/profile/profile.page'; @@ -29,10 +27,14 @@ import { UserStatsAccordion, EnableLabelerPage, } from '@/modules/worker/hcaptcha-labeling'; -import { SignUpWorkerPage } from '@/modules/signup/worker'; +import { + WorkerEmailVerificationProcessPage, + WorkerVerifyEmailPage, +} from '@/modules/worker/email-verification'; import { SignInWorkerPage } from '@/modules/signin/worker'; import { JobsDiscoveryPage } from '@/modules/worker/jobs-discovery'; import { WorkerProfilePage } from '@/modules/worker/profile'; +import { SignUpWorkerPage } from '@/modules/signup/worker'; export const unprotectedRoutes: RouteProps[] = [ { @@ -57,11 +59,11 @@ export const unprotectedRoutes: RouteProps[] = [ }, { path: routerPaths.worker.emailVerification, - element: , + element: , }, { path: routerPaths.worker.verifyEmail, - element: , + element: , }, { path: routerPaths.worker.sendResetLink, diff --git a/packages/apps/human-app/frontend/src/shared/i18n/en.json b/packages/apps/human-app/frontend/src/shared/i18n/en.json index 3ec5cc3083..08b9509a74 100644 --- a/packages/apps/human-app/frontend/src/shared/i18n/en.json +++ b/packages/apps/human-app/frontend/src/shared/i18n/en.json @@ -154,7 +154,10 @@ "emailVerification": { "title": "Email Verified", "description": "You are ready to go. Your email has been successfully verified!", - "btn": "Sign In" + "btn": "Sign In", + "errors": { + "noToken": "No token provided." + } }, "sendResetLinkForm": { "fields": { @@ -173,7 +176,8 @@ "paragraph3": "<1>Can't find our email? Please check your spam folder, or click below and we will send you a new one.", "btn": "Resend Email", "btnAfterSend": "Resend Email", - "paragraph4": "<1>Having trouble? Please <2>Contact Support" + "paragraph4": "<1>Having trouble? Please <2>Contact Support", + "successResent": "Verification email has been successfully resent." }, "resetPassword": { "fields": { From b7dc8a98282a41350f9f78d1028760bacde44aae Mon Sep 17 00:00:00 2001 From: mpblocky <185767042+mpblocky@users.noreply.github.com> Date: Fri, 28 Feb 2025 17:03:46 +0100 Subject: [PATCH 16/27] [HUMAN App] chore: move operator profile components to one module (#3134) --- .../src/modules/operator/profile/components/index.ts | 2 ++ .../components}/profile-disable-button.tsx | 0 .../components}/profile-enable-button.tsx | 0 .../frontend/src/modules/operator/profile/hooks/index.ts | 1 + .../modules/operator/{ => profile}/hooks/use-get-stats.ts | 0 .../frontend/src/modules/operator/profile/index.ts | 1 + .../modules/operator/{views => }/profile/profile.page.tsx | 7 +++---- packages/apps/human-app/frontend/src/router/routes.tsx | 2 +- .../frontend/src/shared/components/ui/profile/index.ts | 1 + .../components/ui/{ => profile}/profile-list-item.tsx | 2 +- 10 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts rename packages/apps/human-app/frontend/src/modules/operator/{components/profile => profile/components}/profile-disable-button.tsx (100%) rename packages/apps/human-app/frontend/src/modules/operator/{components/profile => profile/components}/profile-enable-button.tsx (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts rename packages/apps/human-app/frontend/src/modules/operator/{ => profile}/hooks/use-get-stats.ts (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/operator/profile/index.ts rename packages/apps/human-app/frontend/src/modules/operator/{views => }/profile/profile.page.tsx (95%) create mode 100644 packages/apps/human-app/frontend/src/shared/components/ui/profile/index.ts rename packages/apps/human-app/frontend/src/shared/components/ui/{ => profile}/profile-list-item.tsx (98%) diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts new file mode 100644 index 0000000000..dbcc41c656 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts @@ -0,0 +1,2 @@ +export * from './profile-disable-button'; +export * from './profile-enable-button'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/components/profile/profile-disable-button.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-disable-button.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/operator/components/profile/profile-disable-button.tsx rename to packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-disable-button.tsx diff --git a/packages/apps/human-app/frontend/src/modules/operator/components/profile/profile-enable-button.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-enable-button.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/operator/components/profile/profile-enable-button.tsx rename to packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-enable-button.tsx diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts new file mode 100644 index 0000000000..d67ff6f5e3 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-get-stats'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/hooks/use-get-stats.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-get-stats.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/operator/hooks/use-get-stats.ts rename to packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-get-stats.ts diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/index.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/index.ts new file mode 100644 index 0000000000..30c1db5b64 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/operator/profile/index.ts @@ -0,0 +1 @@ +export * from './profile.page'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/views/profile/profile.page.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/profile.page.tsx similarity index 95% rename from packages/apps/human-app/frontend/src/modules/operator/views/profile/profile.page.tsx rename to packages/apps/human-app/frontend/src/modules/operator/profile/profile.page.tsx index c128bd16dc..a65e3d487b 100644 --- a/packages/apps/human-app/frontend/src/modules/operator/views/profile/profile.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/operator/profile/profile.page.tsx @@ -9,12 +9,11 @@ import { PageCardLoader, } from '@/shared/components/ui/page-card'; import { getErrorMessageForError } from '@/shared/errors'; -import { ProfileDisableButton } from '@/modules/operator/components/profile/profile-disable-button'; -import { ProfileListItem } from '@/shared/components/ui/profile-list-item'; -import { useGetOperatorStats } from '@/modules/operator/hooks/use-get-stats'; -import { ProfileEnableButton } from '@/modules/operator/components/profile/profile-enable-button'; import { CheckmarkIcon, LockerIcon } from '@/shared/components/ui/icons'; import { useColorMode } from '@/shared/contexts/color-mode'; +import { ProfileListItem } from '@/shared/components/ui/profile'; +import { useGetOperatorStats } from './hooks'; +import { ProfileDisableButton, ProfileEnableButton } from './components'; export function OperatorProfilePage() { const { colorPalette } = useColorMode(); diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx index 0f8b8935af..0f07b20ec3 100644 --- a/packages/apps/human-app/frontend/src/router/routes.tsx +++ b/packages/apps/human-app/frontend/src/router/routes.tsx @@ -19,7 +19,6 @@ import { EditExistingKeysSuccessPage } from '@/modules/operator/views/sign-up/ed import { AddKeysOperatorPage } from '@/modules/operator/views/sign-up/add-keys.page'; import { AddStakeOperatorPage } from '@/modules/operator/views/sign-up/add-stake.page'; import { ConnectWalletOperatorPage } from '@/modules/operator/views/sign-up/connect-wallet.page'; -import { OperatorProfilePage } from '@/modules/operator/views/profile/profile.page'; import { Playground } from '@/modules/playground/views/playground.page'; import { HomePage } from '@/modules/homepage/views/home.page'; import { @@ -35,6 +34,7 @@ import { SignInWorkerPage } from '@/modules/signin/worker'; import { JobsDiscoveryPage } from '@/modules/worker/jobs-discovery'; import { WorkerProfilePage } from '@/modules/worker/profile'; import { SignUpWorkerPage } from '@/modules/signup/worker'; +import { OperatorProfilePage } from '@/modules/operator/profile'; export const unprotectedRoutes: RouteProps[] = [ { diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/profile/index.ts b/packages/apps/human-app/frontend/src/shared/components/ui/profile/index.ts new file mode 100644 index 0000000000..232a412eb0 --- /dev/null +++ b/packages/apps/human-app/frontend/src/shared/components/ui/profile/index.ts @@ -0,0 +1 @@ +export * from './profile-list-item'; diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/profile-list-item.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/profile/profile-list-item.tsx similarity index 98% rename from packages/apps/human-app/frontend/src/shared/components/ui/profile-list-item.tsx rename to packages/apps/human-app/frontend/src/shared/components/ui/profile/profile-list-item.tsx index 645c02e693..759c6fb8cd 100644 --- a/packages/apps/human-app/frontend/src/shared/components/ui/profile-list-item.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/ui/profile/profile-list-item.tsx @@ -15,7 +15,7 @@ export function ProfileListItem({ header, paragraph, isStatusListItem, -}: ProfileListItemProps) { +}: Readonly) { const { colorPalette } = useColorMode(); const { t } = useTranslation(); From c58e0334f297441b49d3a27c49e018a4e35eb0ce Mon Sep 17 00:00:00 2001 From: Dmitry Nechay Date: Mon, 3 Mar 2025 13:00:40 +0300 Subject: [PATCH 17/27] [HUMAN App] fix: show governance banner only for workers (#3139) --- packages/apps/human-app/frontend/src/router/router.tsx | 1 + .../src/shared/components/layout/protected/layout.tsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/apps/human-app/frontend/src/router/router.tsx b/packages/apps/human-app/frontend/src/router/router.tsx index 685ac94016..ace123b038 100644 --- a/packages/apps/human-app/frontend/src/router/router.tsx +++ b/packages/apps/human-app/frontend/src/router/router.tsx @@ -69,6 +69,7 @@ export function Router() { renderHCaptchaStatisticsDrawer={(isOpen) => ( )} + renderGovernanceBanner /> } diff --git a/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx b/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx index 85b6c8cf20..9080d7296b 100644 --- a/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/layout/protected/layout.tsx @@ -38,6 +38,7 @@ export function Layout({ pageHeaderProps, renderDrawer, renderHCaptchaStatisticsDrawer, + renderGovernanceBanner, }: { pageHeaderProps: PageHeaderProps; renderDrawer: ( @@ -45,6 +46,7 @@ export function Layout({ setDrawerOpen: Dispatch> ) => JSX.Element; renderHCaptchaStatisticsDrawer?: (isOpen: boolean) => JSX.Element; + renderGovernanceBanner?: boolean; }) { const layoutElementRef = useRef(); const isHCaptchaLabelingPage = useIsHCaptchaLabelingPage(); @@ -118,7 +120,7 @@ export function Layout({ }, }} > - + {renderGovernanceBanner && } From d7e9a3a7432d0a75002fd767fe4b18332840c0d2 Mon Sep 17 00:00:00 2001 From: adrian-oleskiewicz Date: Mon, 3 Mar 2025 11:14:33 +0100 Subject: [PATCH 18/27] [HUMAN App] refactor: available jobs service (#3032) --- .../desktop/available-jobs-table.tsx | 2 +- .../mobile/available-jobs-table-mobile.tsx | 10 +++--- .../worker/services/available-jobs-data.ts | 35 ++++++++++--------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx index 3c11391ce7..c18401185b 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx @@ -166,7 +166,7 @@ const getColumns = ( export function AvailableJobsTable({ chainIdsEnabled, -}: AvailableJobsTableProps) { +}: Readonly) { const { colorPalette, isDarkMode } = useColorMode(); const { setSearchEscrowAddress, diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx index f41a2b2511..fc503eb452 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx @@ -8,8 +8,6 @@ import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store import { Alert } from '@/shared/components/ui/alert'; import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; import { getErrorMessageForError } from '@/shared/errors'; -import type { AvailableJob } from '@/modules/worker/services/available-jobs-data'; -import { useInfiniteGetAvailableJobsData } from '@/modules/worker/services/available-jobs-data'; import { Loader } from '@/shared/components/ui/loader'; import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; import { Chip } from '@/shared/components/ui/chip'; @@ -19,6 +17,10 @@ import { useColorMode } from '@/shared/contexts/color-mode'; import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; import { EscrowAddressSearchForm } from '@/modules/worker/components/jobs/escrow-address-search-form'; import { AvailableJobsAssignJobButton } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-assign-job-button'; +import { + type AvailableJob, + useInfiniteAvailableJobsQuery, +} from '@/modules/worker/services/available-jobs-data'; interface AvailableJobsTableMobileProps { setIsMobileFilterDrawerOpen: Dispatch>; @@ -26,7 +28,7 @@ interface AvailableJobsTableMobileProps { export function AvailableJobsTableMobile({ setIsMobileFilterDrawerOpen, -}: AvailableJobsTableMobileProps) { +}: Readonly) { const { colorPalette } = useColorMode(); const [allPages, setAllPages] = useState([]); @@ -37,7 +39,7 @@ export function AvailableJobsTableMobile({ error: tableError, fetchNextPage, hasNextPage, - } = useInfiniteGetAvailableJobsData(); + } = useInfiniteAvailableJobsQuery(); const { filterParams, setPageParams, resetFilterParams } = useJobsFilterStore(); const { t } = useTranslation(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts b/packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts index 1f18fc8981..f0dfff4f6b 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts @@ -27,22 +27,22 @@ export type AvailableJobsSuccessResponse = z.infer< typeof availableJobsSuccessResponseSchema >; -type GetJobTableDataDto = JobsFilterStoreProps['filterParams'] & { +type JobTableQueryParams = JobsFilterStoreProps['filterParams'] & { oracle_address: string; }; const getAvailableJobsTableData = async ( - dto: GetJobTableDataDto, + params: JobTableQueryParams, abortSignal: AbortSignal ) => { + const endpoint = `${apiPaths.worker.jobs.path}?${stringifyUrlQueryObject(params)}`; + return apiClient( - `${apiPaths.worker.jobs.path}?${stringifyUrlQueryObject({ ...dto })}`, + endpoint, { authenticated: true, successSchema: availableJobsSuccessResponseSchema, - options: { - method: 'GET', - }, + options: { method: 'GET' }, }, abortSignal ); @@ -59,19 +59,20 @@ export function useGetAvailableJobsData() { }); } -export function useInfiniteGetAvailableJobsData() { +export function useInfiniteAvailableJobsQuery() { const { filterParams } = useJobsFilterStore(); - const { address: oracle_address } = useParams<{ address: string }>(); - const dto = { ...filterParams, oracle_address: oracle_address ?? '' }; + const { address: oracleAddress } = useParams<{ address: string }>(); - return useInfiniteQuery({ + const queryParams = { + ...filterParams, + oracle_address: oracleAddress ?? '', + }; + + return useInfiniteQuery({ + queryKey: ['availableJobsInfinite', queryParams], + queryFn: ({ signal }) => getAvailableJobsTableData(queryParams, signal), initialPageParam: 0, - queryKey: ['availableJobsInfinite', dto], - queryFn: ({ signal }) => getAvailableJobsTableData(dto, signal), - getNextPageParam: (pageParams) => { - return pageParams.total_pages - 1 <= pageParams.page - ? undefined - : pageParams.page; - }, + getNextPageParam: (lastPage) => + lastPage.total_pages - 1 <= lastPage.page ? undefined : lastPage.page, }); } From 0bc3d5a88eb22eefa23d3231e1820a2096c8c90a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:59:18 +0100 Subject: [PATCH 19/27] chore(deps): bump @mui/icons-material from 6.4.4 to 6.4.6 (#3143) Bumps [@mui/icons-material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material) from 6.4.4 to 6.4.6. - [Release notes](https://github.com/mui/material-ui/releases) - [Changelog](https://github.com/mui/material-ui/blob/v6.4.6/CHANGELOG.md) - [Commits](https://github.com/mui/material-ui/commits/v6.4.6/packages/mui-icons-material) --- updated-dependencies: - dependency-name: "@mui/icons-material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/dashboard/client/package.json | 2 +- packages/apps/faucet/client/package.json | 2 +- packages/apps/human-app/frontend/package.json | 2 +- .../apps/job-launcher/client/package.json | 2 +- packages/apps/staking/package.json | 2 +- yarn.lock | 39 +++++++++++++++---- 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json index 363ccb9972..b926e29535 100644 --- a/packages/apps/dashboard/client/package.json +++ b/packages/apps/dashboard/client/package.json @@ -16,7 +16,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@human-protocol/sdk": "*", - "@mui/icons-material": "^6.2.0", + "@mui/icons-material": "^6.4.6", "@mui/material": "^5.15.18", "@mui/styled-engine-sc": "6.4.0", "@mui/x-data-grid": "^7.23.2", diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json index 29e2135a43..006c225494 100644 --- a/packages/apps/faucet/client/package.json +++ b/packages/apps/faucet/client/package.json @@ -6,7 +6,7 @@ "license": "MIT", "dependencies": { "@human-protocol/sdk": "*", - "@mui/icons-material": "^6.2.0", + "@mui/icons-material": "^6.4.6", "@mui/material": "^5.16.7", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index 2df5b49bc3..ceefa99ef4 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -20,7 +20,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@hookform/resolvers": "^3.3.4", "@human-protocol/sdk": "*", - "@mui/icons-material": "^6.2.0", + "@mui/icons-material": "^6.4.6", "@mui/material": "^5.16.7", "@mui/x-date-pickers": "^7.23.6", "@reown/appkit": "1.3.2", diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 4067b67ccf..07249cbce7 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -7,7 +7,7 @@ "@emotion/styled": "^11.10.5", "@hcaptcha/react-hcaptcha": "^1.10.1", "@human-protocol/sdk": "*", - "@mui/icons-material": "^6.2.0", + "@mui/icons-material": "^6.4.6", "@mui/lab": "^5.0.0-alpha.141", "@mui/material": "^5.16.7", "@mui/x-date-pickers": "^7.23.6", diff --git a/packages/apps/staking/package.json b/packages/apps/staking/package.json index ab2da99cb5..460c1b4fc4 100644 --- a/packages/apps/staking/package.json +++ b/packages/apps/staking/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@human-protocol/sdk": "*", - "@mui/icons-material": "^6.3.1", + "@mui/icons-material": "^6.4.6", "@mui/material": "^5.16.7", "@tanstack/query-sync-storage-persister": "^5.59.0", "@tanstack/react-query": "^5.60.5", diff --git a/yarn.lock b/yarn.lock index 9809149e0d..52c3f8670b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3757,10 +3757,10 @@ resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.14.tgz#e6536f1b6caa873f7915fbf9703fdc840a5a98d9" integrity sha512-sbjXW+BBSvmzn61XyTMun899E7nGPTXwqD9drm1jBUAvWEhJpPFIRxwQQiATWZnd9rvdxtnhhdsDxEGWI0jxqA== -"@mui/icons-material@^6.2.0", "@mui/icons-material@^6.3.1": - version "6.4.4" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-6.4.4.tgz#cbc2c9e9c9bbb66f4062085dc69c0b886cd7f888" - integrity sha512-uF1chGaoFmYdRUomK6f8kgJfWosk9A3HXWiVD0vQm+2mE7f25eTQ1E8RRO11LXpnUBqu8Rbv/uGlpnjT/u1Ksg== +"@mui/icons-material@^6.4.6": + version "6.4.6" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-6.4.6.tgz#a26eaeae2f7f1359b48dac3fe8a8eec61640c325" + integrity sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ== dependencies: "@babel/runtime" "^7.26.0" @@ -18449,7 +18449,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18558,7 +18567,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18572,6 +18581,13 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -20485,7 +20501,7 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -20503,6 +20519,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From bff7aafec2b976c8aaaec3d7d96025b6b76346b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:00:52 +0100 Subject: [PATCH 20/27] [Job Launcher] Move routing protocol (#3149) * feat: move routing protocol module and interfaces * refactor: update import paths for RoutingProtocolService in job and cron-job services --- .../src/modules/cron-job/cron-job.service.spec.ts | 2 +- .../job-launcher/server/src/modules/job/job.module.ts | 6 +++--- .../server/src/modules/job/job.service.spec.ts | 2 +- .../server/src/modules/job/job.service.ts | 2 +- .../routing-protocol.interface.ts | 0 .../routing-protocol/routing-protocol.module.ts | 11 +++++++++++ .../routing-protocol.service.spec.ts | 0 .../routing-protocol.service.ts | 0 8 files changed, 17 insertions(+), 6 deletions(-) rename packages/apps/job-launcher/server/src/modules/{job => routing-protocol}/routing-protocol.interface.ts (100%) create mode 100644 packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.module.ts rename packages/apps/job-launcher/server/src/modules/{job => routing-protocol}/routing-protocol.service.spec.ts (100%) rename packages/apps/job-launcher/server/src/modules/{job => routing-protocol}/routing-protocol.service.ts (100%) diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts index 8363549869..05ab5b1cf5 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts @@ -36,7 +36,7 @@ import { PaymentService } from '../payment/payment.service'; import { JobRepository } from '../job/job.repository'; import { PaymentRepository } from '../payment/payment.repository'; import { ConfigService } from '@nestjs/config'; -import { RoutingProtocolService } from '../job/routing-protocol.service'; +import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; import { WebhookEntity } from '../webhook/webhook.entity'; import { WebhookStatus } from '../../common/enums/webhook'; import { WebhookRepository } from '../webhook/webhook.repository'; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.module.ts b/packages/apps/job-launcher/server/src/modules/job/job.module.ts index 846e80aa9e..71795c5100 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.module.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.module.ts @@ -9,7 +9,6 @@ import { HttpModule } from '@nestjs/axios'; import { PaymentModule } from '../payment/payment.module'; import { JobRepository } from './job.repository'; import { Web3Module } from '../web3/web3.module'; -import { RoutingProtocolService } from './routing-protocol.service'; import { EncryptionModule } from '../encryption/encryption.module'; import { StorageModule } from '../storage/storage.module'; import { AuthModule } from '../auth/auth.module'; @@ -18,6 +17,7 @@ import { WebhookRepository } from '../webhook/webhook.repository'; import { MutexManagerService } from '../mutex/mutex-manager.service'; import { QualificationModule } from '../qualification/qualification.module'; import { WhitelistModule } from '../whitelist/whitelist.module'; +import { RoutingProtocolModule } from '../routing-protocol/routing-protocol.module'; @Module({ imports: [ @@ -31,16 +31,16 @@ import { WhitelistModule } from '../whitelist/whitelist.module'; StorageModule, QualificationModule, WhitelistModule, + RoutingProtocolModule, ], controllers: [JobController], providers: [ Logger, JobService, JobRepository, - RoutingProtocolService, WebhookRepository, MutexManagerService, ], - exports: [JobService, RoutingProtocolService], + exports: [JobService], }) export class JobModule {} diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 10e389b10e..9b1fbc2bec 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -23,7 +23,7 @@ import { JobRepository } from './job.repository'; import { WebhookRepository } from '../webhook/webhook.repository'; import { JobService } from './job.service'; import { PaymentRepository } from '../payment/payment.repository'; -import { RoutingProtocolService } from './routing-protocol.service'; +import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; import { StorageService } from '../storage/storage.service'; import { ServerConfigService } from '../../common/config/server-config.service'; import { AuthConfigService } from '../../common/config/auth-config.service'; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 40e9118f3b..119c91378b 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -70,7 +70,6 @@ import { } from './job.dto'; import { JobEntity } from './job.entity'; import { JobRepository } from './job.repository'; -import { RoutingProtocolService } from './routing-protocol.service'; import { CANCEL_JOB_STATUSES, HCAPTCHA_BOUNDING_BOX_MAX_POINTS, @@ -122,6 +121,7 @@ import { ModuleRef } from '@nestjs/core'; import { QualificationService } from '../qualification/qualification.service'; import { WhitelistService } from '../whitelist/whitelist.service'; import { UserEntity } from '../user/user.entity'; +import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; @Injectable() export class JobService { diff --git a/packages/apps/job-launcher/server/src/modules/job/routing-protocol.interface.ts b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.interface.ts similarity index 100% rename from packages/apps/job-launcher/server/src/modules/job/routing-protocol.interface.ts rename to packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.interface.ts diff --git a/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.module.ts b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.module.ts new file mode 100644 index 0000000000..1ae2b644ba --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { RoutingProtocolService } from './routing-protocol.service'; +import { Web3Module } from '../web3/web3.module'; +import { ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [Web3Module, ConfigModule], + providers: [RoutingProtocolService], + exports: [RoutingProtocolService], +}) +export class RoutingProtocolModule {} diff --git a/packages/apps/job-launcher/server/src/modules/job/routing-protocol.service.spec.ts b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.spec.ts similarity index 100% rename from packages/apps/job-launcher/server/src/modules/job/routing-protocol.service.spec.ts rename to packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.spec.ts diff --git a/packages/apps/job-launcher/server/src/modules/job/routing-protocol.service.ts b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.ts similarity index 100% rename from packages/apps/job-launcher/server/src/modules/job/routing-protocol.service.ts rename to packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.ts From 30f7b5b0d916e9c2f4c11abd51645c2c2e855202 Mon Sep 17 00:00:00 2001 From: kilavvy <140459108+kilavvy@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:06:32 +0100 Subject: [PATCH 21/27] fix: typos in documentation files (#3138) * Update README.md * Update MetaHumanGovernor.ts * Update legacy_encryption.py --- packages/core/README.md | 2 +- packages/core/test/MetaHumanGovernor.ts | 2 +- .../human-protocol-sdk/human_protocol_sdk/legacy_encryption.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index 0ff2c114ad..450664991d 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -47,7 +47,7 @@ On the other hand, it can be cancelled anytime. Pay out the workers. Final result URL is recorded. If the escrow is fully paid out, escrow status is changed to `Paid`, otherwise it's changed to `Partial`. - > Trusted handlers, and reputation oracle can call this funciton. + > Trusted handlers, and reputation oracle can call this function. - `complete()` diff --git a/packages/core/test/MetaHumanGovernor.ts b/packages/core/test/MetaHumanGovernor.ts index edafb96a59..02c685132f 100644 --- a/packages/core/test/MetaHumanGovernor.ts +++ b/packages/core/test/MetaHumanGovernor.ts @@ -1039,7 +1039,7 @@ describe('MetaHumanGovernor', function () { ).to.be.revertedWithCustomError(governor, 'MessageAlreadyProcessed'); }); - it('should reverts to receive message when intended receipient is not different', async function () { + it('should reverts to receive message when intended recipient is not different', async function () { const proposalId = await createBasicProposal( daoSpoke, wormholeMockForDaoSpoke, diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/legacy_encryption.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/legacy_encryption.py index 56e6f1d587..5ce0f7c801 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/legacy_encryption.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/legacy_encryption.py @@ -50,7 +50,7 @@ class Encryption: """ ECIES using AES256 and HMAC-SHA-256-32 """ CIPHER = AES - """ Cipher algorithm defintion. """ + """ Cipher algorithm definition. """ MODE = CTR """ Cipher mode definition. """ From b08c409ee6519bd7b50d1a73605223d65124808f Mon Sep 17 00:00:00 2001 From: Dmitry Nechay Date: Tue, 4 Mar 2025 14:57:09 +0300 Subject: [PATCH 22/27] [Reputation Oracle] refactor: hCaptcha service (#3148) --- .../reputation-oracle/server/package.json | 3 +- .../server/src/app.module.ts | 2 - .../src/integrations/hcaptcha/fixtures.ts | 11 + .../src/integrations/hcaptcha/hcaptcha.dto.ts | 18 - .../hcaptcha/hcaptcha.guard.spec.ts | 145 ++++ .../hcaptcha/hcaptcha.guard.ts} | 9 +- .../integrations/hcaptcha/hcaptcha.module.ts | 8 +- .../hcaptcha/hcaptcha.service.spec.ts | 645 +++++++----------- .../integrations/hcaptcha/hcaptcha.service.ts | 107 +-- .../server/src/integrations/hcaptcha/types.ts | 43 ++ .../src/modules/auth/auth.controller.ts | 2 +- .../kyc/kyc-webhook-auth.guard.ts} | 0 .../server/src/modules/kyc/kyc.controller.ts | 2 +- .../src/modules/user/user.controller.ts | 2 +- .../server/src/modules/user/user.service.ts | 13 +- .../server/test/mock-creators/nest.ts | 29 +- yarn.lock | 11 +- 17 files changed, 562 insertions(+), 488 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts delete mode 100644 packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.dto.ts create mode 100644 packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts rename packages/apps/reputation-oracle/server/src/{common/guards/hcaptcha.ts => integrations/hcaptcha/hcaptcha.guard.ts} (87%) create mode 100644 packages/apps/reputation-oracle/server/src/integrations/hcaptcha/types.ts rename packages/apps/reputation-oracle/server/src/{common/guards/kyc-webhook.auth.ts => modules/kyc/kyc-webhook-auth.guard.ts} (100%) diff --git a/packages/apps/reputation-oracle/server/package.json b/packages/apps/reputation-oracle/server/package.json index abb7266a4a..76ab5cd62b 100644 --- a/packages/apps/reputation-oracle/server/package.json +++ b/packages/apps/reputation-oracle/server/package.json @@ -31,7 +31,7 @@ "dependencies": { "@human-protocol/core": "*", "@human-protocol/sdk": "*", - "@nestjs/axios": "^3.1.2", + "@nestjs/axios": "^3.1.3", "@nestjs/common": "^10.2.7", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.3.10", @@ -45,6 +45,7 @@ "@nestjs/typeorm": "^10.0.1", "@sendgrid/mail": "^8.1.3", "@types/passport-jwt": "^4.0.1", + "axios": "^1.8.1", "bcrypt": "^5.1.1", "body-parser": "^1.20.3", "class-transformer": "^0.5.1", diff --git a/packages/apps/reputation-oracle/server/src/app.module.ts b/packages/apps/reputation-oracle/server/src/app.module.ts index b36959d953..1388bd6e0e 100644 --- a/packages/apps/reputation-oracle/server/src/app.module.ts +++ b/packages/apps/reputation-oracle/server/src/app.module.ts @@ -16,7 +16,6 @@ import { KycModule } from './modules/kyc/kyc.module'; import { CronJobModule } from './modules/cron-job/cron-job.module'; import { PayoutModule } from './modules/payout/payout.module'; import { EnvConfigModule } from './config/config.module'; -import { HCaptchaModule } from './integrations/hcaptcha/hcaptcha.module'; import { ExceptionFilter } from './common/filters/exception.filter'; import { QualificationModule } from './modules/qualification/qualification.module'; import { EscrowCompletionModule } from './modules/escrow-completion/escrow-completion.module'; @@ -60,7 +59,6 @@ import Environment from './utils/environment'; }), EnvConfigModule, DatabaseModule, - HCaptchaModule, AuthModule, CronJobModule, EmailModule, diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts new file mode 100644 index 0000000000..6c23bee532 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts @@ -0,0 +1,11 @@ +import { faker } from '@faker-js/faker'; +import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; + +export const mockHCaptchaConfigService: Partial = { + siteKey: faker.string.uuid(), + apiKey: faker.string.uuid(), + secret: `E0_${faker.string.alphanumeric()}`, + protectionURL: faker.internet.url(), + labelingURL: faker.internet.url(), + defaultLabelerLang: faker.location.language().alpha2, +}; diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.dto.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.dto.ts deleted file mode 100644 index 54b76dd95e..0000000000 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -export class hCaptchaApiParams { - public ip?: string; -} - -export class hCaptchaVerifyToken extends hCaptchaApiParams { - public token: string; -} - -export class hCaptchaRegisterLabeler extends hCaptchaApiParams { - public email: string; - public language: string; - public country: string; - public address: string; -} - -export class hCaptchaGetLabeler extends hCaptchaApiParams { - public email: string; -} diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts new file mode 100644 index 0000000000..83d3417863 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts @@ -0,0 +1,145 @@ +import { faker } from '@faker-js/faker'; +import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common'; + +import { + createExecutionContextMock, + ExecutionContextMock, +} from '../../../test/mock-creators/nest'; + +import { HCaptchaGuard } from './hcaptcha.guard'; +import { AuthConfigService } from 'src/config/auth-config.service'; +import { HCaptchaService } from './hcaptcha.service'; + +const mockHCaptchaService = { + verifyToken: jest.fn(), +}; +const mockAuthConfigService: Partial = { + humanAppEmail: faker.internet.email(), +}; + +describe('HCaptchaGuard', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('canActivate', () => { + let hCaptchaGuard: HCaptchaGuard; + let executionContextMock: ExecutionContextMock; + + beforeAll(() => { + hCaptchaGuard = new HCaptchaGuard( + mockHCaptchaService as unknown as HCaptchaService, + mockAuthConfigService as unknown as AuthConfigService, + ); + }); + + beforeEach(() => { + executionContextMock = createExecutionContextMock(); + }); + + it('should return true and skip verify if human app signin', async () => { + const request = { + path: '/auth/web2/signin', + body: { + email: mockAuthConfigService.humanAppEmail, + }, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + const result = await hCaptchaGuard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + + expect(result).toBe(true); + }); + + it('should verify and return true if not human app signin', async () => { + const testToken = faker.string.alphanumeric(); + + const request = { + path: '/auth/web2/signin', + body: { + email: faker.internet.email(), + h_captcha_token: testToken, + }, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + mockHCaptchaService.verifyToken.mockResolvedValueOnce(true); + + const result = await hCaptchaGuard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + + expect(result).toBe(true); + expect(mockHCaptchaService.verifyToken).toHaveBeenCalledTimes(1); + expect(mockHCaptchaService.verifyToken).toHaveBeenCalledWith(testToken); + }); + + it('should verify and return true if not signin route', async () => { + const testToken = faker.string.alphanumeric(); + + const request = { + path: `/not-signin-route`, + body: { + email: mockAuthConfigService.humanAppEmail, + h_captcha_token: testToken, + }, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + mockHCaptchaService.verifyToken.mockResolvedValueOnce(true); + + const result = await hCaptchaGuard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + + expect(result).toBe(true); + expect(mockHCaptchaService.verifyToken).toHaveBeenCalledTimes(1); + expect(mockHCaptchaService.verifyToken).toHaveBeenCalledWith(testToken); + }); + + it('should throw bad request exception if token is not provided', async () => { + const request = { + body: { + email: mockAuthConfigService.humanAppEmail, + }, + }; + executionContextMock.__getRequest.mockReturnValueOnce(request); + + let thrownError; + try { + await hCaptchaGuard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + } catch (error) { + thrownError = error; + } + expect(thrownError).toBeInstanceOf(HttpException); + expect(thrownError.message).toBe('hCaptcha token not provided'); + expect(thrownError.status).toBe(HttpStatus.BAD_REQUEST); + }); + + it('should throw bad request exception if token is not verified', async () => { + executionContextMock.__getRequest.mockReturnValueOnce({ + body: { + h_captcha_token: faker.string.alphanumeric(), + }, + }); + + mockHCaptchaService.verifyToken.mockResolvedValueOnce(false); + + let thrownError; + try { + await hCaptchaGuard.canActivate( + executionContextMock as unknown as ExecutionContext, + ); + } catch (error) { + thrownError = error; + } + expect(thrownError).toBeInstanceOf(HttpException); + expect(thrownError.message).toBe('Invalid hCaptcha token'); + expect(thrownError.status).toBe(HttpStatus.BAD_REQUEST); + }); + }); +}); diff --git a/packages/apps/reputation-oracle/server/src/common/guards/hcaptcha.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.ts similarity index 87% rename from packages/apps/reputation-oracle/server/src/common/guards/hcaptcha.ts rename to packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.ts index ad58229bfd..c7d83ecb53 100644 --- a/packages/apps/reputation-oracle/server/src/common/guards/hcaptcha.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.ts @@ -6,8 +6,8 @@ import { HttpException, } from '@nestjs/common'; import { Request } from 'express'; -import { HCaptchaService } from '../../integrations/hcaptcha/hcaptcha.service'; import { AuthConfigService } from '../../config/auth-config.service'; +import { HCaptchaService } from './hcaptcha.service'; @Injectable() export class HCaptchaGuard implements CanActivate { @@ -15,7 +15,8 @@ export class HCaptchaGuard implements CanActivate { private readonly hCaptchaService: HCaptchaService, private readonly authConfigSerice: AuthConfigService, ) {} - public async canActivate(context: ExecutionContext): Promise { + + async canActivate(context: ExecutionContext): Promise { const request: Request = context.switchToHttp().getRequest(); const { body } = request; @@ -40,9 +41,7 @@ export class HCaptchaGuard implements CanActivate { ); } - const isTokenValid = await this.hCaptchaService.verifyToken({ - token: hCaptchaToken, - }); + const isTokenValid = await this.hCaptchaService.verifyToken(hCaptchaToken); if (!isTokenValid) { throw new HttpException('Invalid hCaptcha token', HttpStatus.BAD_REQUEST); } diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts index ac9bbd9bf5..92d8dceb26 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.module.ts @@ -1,9 +1,13 @@ +import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { HCaptchaService } from './hcaptcha.service'; -import { HttpModule } from '@nestjs/axios'; @Module({ - imports: [HttpModule], + imports: [ + HttpModule.register({ + validateStatus: () => true, + }), + ], providers: [HCaptchaService], exports: [HCaptchaService], }) diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts index 16591a5349..170b400fb2 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts @@ -1,527 +1,370 @@ -import { HCaptchaService } from './hcaptcha.service'; +import { faker } from '@faker-js/faker'; +import { HttpService } from '@nestjs/axios'; import { Test } from '@nestjs/testing'; -import { ConfigService } from '@nestjs/config'; +import { ethers } from 'ethers'; + +import { HCaptchaService } from './hcaptcha.service'; import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; -import { DeepPartial } from 'typeorm'; -import { HttpService } from '@nestjs/axios'; -import { of, firstValueFrom } from 'rxjs'; + import { - MOCK_HCAPTCHA_PROTECTION_URL, - MOCK_HCAPTCHA_LABELING_URL, - mockConfig, -} from '../../../test/constants'; -import { TokenType } from '../../common/enums'; - -jest.mock('rxjs', () => { - const originalModule = jest.requireActual('rxjs'); - return { - ...originalModule, - firstValueFrom: jest.fn(), - }; -}); + createHttpServiceMock, + createHttpServiceRequestError, + createHttpServiceResponse, +} from '../../../test/mock-creators/nest'; +import { generateEthWallet } from '../../../test/fixtures/web3'; + +import { mockHCaptchaConfigService } from './fixtures'; +import { LabelerData, SiteverifyResponse } from './types'; + +const mockHttpService = createHttpServiceMock(); describe('hCaptchaService', () => { let hcaptchaService: HCaptchaService; - let httpService: HttpService; beforeAll(async () => { - const mockHttpService: DeepPartial = { - axiosRef: { - get: jest.fn(), - post: jest.fn(), - }, - }; - const moduleRef = await Test.createTestingModule({ providers: [ - { - provide: ConfigService, - useValue: { - get: jest.fn((key: string) => mockConfig[key]), - getOrThrow: jest.fn((key: string) => { - if (!mockConfig[key]) { - throw new Error(`Configuration key "${key}" does not exist`); - } - return mockConfig[key]; - }), - }, - }, - HCaptchaService, - HCaptchaConfigService, { provide: HttpService, useValue: mockHttpService, }, + { + provide: HCaptchaConfigService, + useValue: mockHCaptchaConfigService, + }, + HCaptchaService, ], }).compile(); - httpService = moduleRef.get(HttpService); hcaptchaService = moduleRef.get(HCaptchaService); }); - describe('verifyToken', () => { - it('should verify hCaptcha auth token successfully', async () => { - const mockData = { - ip: '127.0.0.1', - token: 'token', - type: TokenType.AUTH, - }; - - const mockResponseData = { success: true }; - - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - data: mockResponseData, - }); - }); - - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - data: mockResponseData, - }); + afterEach(() => { + jest.resetAllMocks(); + }); - const result = await hcaptchaService.verifyToken(mockData); + describe('verifyToken', () => { + it('should call configured siteverify URL', async () => { + await hcaptchaService.verifyToken(faker.string.alphanumeric()); - expect(result).toEqual(true); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, + expect(mockHttpService.post).toHaveBeenCalledTimes(1); + expect(mockHttpService.post).toHaveBeenCalledWith( + `${mockHCaptchaConfigService.protectionURL}/siteverify`, {}, - { params: expect.any(Object) }, + { + params: expect.objectContaining({ + secret: mockHCaptchaConfigService.secret, + sitekey: mockHCaptchaConfigService.siteKey, + }), + }, ); }); - it('should verify hCaptcha auth token successfully without IP', async () => { - const mockData = { - url: 'https://example.com', - secret: 'secret-key', - sitekey: 'site-key', - token: 'token', - type: TokenType.AUTH, + it('returns false if response status is not 200', async () => { + const response: SiteverifyResponse = { + success: true, }; + mockHttpService.post.mockReturnValueOnce( + createHttpServiceResponse(204, response), + ); - const mockResponseData = { success: true }; - - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - data: mockResponseData, - }); - }); - - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - data: mockResponseData, - }); - - const result = await hcaptchaService.verifyToken(mockData); - - expect(result).toEqual(true); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, - {}, - { params: expect.any(Object) }, + const result = await hcaptchaService.verifyToken( + faker.string.alphanumeric(), ); + + expect(result).toBe(false); }); - it('should verify hCaptcha labeling token successfully', async () => { - const mockData = { - ip: '127.0.0.1', - token: 'token', - type: TokenType.EXCHANGE, + it('returns false if 200 but verification is not successfull', async () => { + const response: SiteverifyResponse = { + success: false, }; - const mockResponseData = { success: true }; + mockHttpService.post.mockReturnValueOnce( + createHttpServiceResponse(200, response), + ); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - data: mockResponseData, - }); - }); + const result = await hcaptchaService.verifyToken( + faker.string.alphanumeric(), + ); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - data: mockResponseData, - }); + expect(result).toBe(false); + }); - const result = await hcaptchaService.verifyToken(mockData); + it('returns false if response has no data', async () => { + mockHttpService.post.mockReturnValueOnce(createHttpServiceResponse(200)); - expect(result).toEqual(true); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, - {}, - { params: expect.any(Object) }, + const result = await hcaptchaService.verifyToken( + faker.string.alphanumeric(), ); + + expect(result).toBe(false); }); - it('should verify hCaptcha labeling token successfully without IP', async () => { - const mockData = { - url: 'https://example.com', - secret: 'secret-key', - sitekey: 'site-key', - token: 'token', - type: TokenType.EXCHANGE, - }; + it('returns false if API request fails', async () => { + mockHttpService.post.mockReturnValueOnce( + createHttpServiceRequestError(new Error()), + ); - const mockResponseData = { success: true }; + const result = await hcaptchaService.verifyToken( + faker.string.alphanumeric(), + ); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - data: mockResponseData, - }); - }); + expect(result).toBe(false); + }); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - data: mockResponseData, - }); + it('should not attach ip and pass verification', async () => { + const testToken = faker.string.alphanumeric(); - const result = await hcaptchaService.verifyToken(mockData); + const response: SiteverifyResponse = { + success: true, + }; - expect(result).toEqual(true); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, - {}, - { params: expect.any(Object) }, + mockHttpService.post.mockReturnValueOnce( + createHttpServiceResponse(200, response), ); - }); - - it('should return false if API response status is not 200', async () => { - const mockData = { - url: 'https://example.com', - secret: 'secret-key', - sitekey: 'site-key', - token: 'token', - type: TokenType.AUTH, - }; - const mockResponseData = { success: false }; + const result = await hcaptchaService.verifyToken(testToken); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 400, - data: mockResponseData, - }); - }); + expect(result).toBe(true); + expect(mockHttpService.post).toHaveBeenCalledTimes(1); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 400, - data: mockResponseData, + const requestConfigArg = mockHttpService.post.mock.calls[0][2]; + expect(requestConfigArg).toEqual({ + params: expect.objectContaining({ + response: testToken, + }), }); - - const result = await hcaptchaService.verifyToken(mockData); - - expect(result).toEqual(false); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, - {}, - { params: expect.any(Object) }, - ); + expect(requestConfigArg).not.toHaveProperty('params.remoteip'); }); - it('should return false if API response does not contain data', async () => { - const mockData = { - url: 'https://example.com', - secret: 'secret-key', - sitekey: 'site-key', - token: 'token', - type: TokenType.AUTH, + it('should attach ip and pass verification', async () => { + const testToken = faker.string.alphanumeric(); + const testIp = faker.internet.ip(); + + const response: SiteverifyResponse = { + success: true, }; - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - }); - }); + mockHttpService.post.mockReturnValueOnce( + createHttpServiceResponse(200, response), + ); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - }); + const result = await hcaptchaService.verifyToken(testToken, testIp); - const result = await hcaptchaService.verifyToken(mockData); + expect(result).toBe(true); - expect(result).toEqual(false); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, + expect(mockHttpService.post).toHaveBeenCalledWith( + expect.any(String), {}, - { params: expect.any(Object) }, + { + params: expect.objectContaining({ + response: testToken, + remoteip: testIp, + }), + }, ); }); + }); - it('should return false if API request fails', async () => { - const mockData = { - url: 'https://example.com', - secret: 'secret-key', - sitekey: 'site-key', - token: 'token', - type: TokenType.AUTH, - }; - - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 401, - }); + describe('registerLabeler', () => { + it('should call configured labeler registration URL', async () => { + await hcaptchaService.registerLabeler({ + email: faker.internet.email(), + evmAddress: generateEthWallet().address, }); - (firstValueFrom as jest.Mock).mockRejectedValue( - new Error('Network Error'), - ); - - const result = await hcaptchaService.verifyToken(mockData); - - expect(result).toEqual(false); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_PROTECTION_URL}/siteverify`, - {}, - { params: expect.any(Object) }, + expect(mockHttpService.post).toHaveBeenCalledTimes(1); + expect(mockHttpService.post).toHaveBeenCalledWith( + `${mockHCaptchaConfigService.labelingURL}/labeler/register`, + expect.objectContaining({ + language: mockHCaptchaConfigService.defaultLabelerLang, + }), + { + params: expect.objectContaining({ + api_key: mockHCaptchaConfigService.apiKey, + }), + }, ); }); - }); - describe('registerLabeler', () => { - it('should register labeler successfully', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - ip: '127.0.0.1', - email: 'test@example.com', - language: 'en', - country: 'US', - address: '0xabcdef1234567890', - }; + it('returns false if response is not 200', async () => { + mockHttpService.post.mockReturnValueOnce(createHttpServiceResponse(204)); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - }); + const result = await hcaptchaService.registerLabeler({ + email: faker.internet.email(), + evmAddress: generateEthWallet().address, }); + expect(result).toBe(false); + }); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - }); + it('returns false if API request fails', async () => { + mockHttpService.post.mockReturnValueOnce( + createHttpServiceRequestError(new Error()), + ); - const result = await hcaptchaService.registerLabeler(mockData); + const result = await hcaptchaService.registerLabeler({ + email: faker.internet.email(), + evmAddress: generateEthWallet().address, + }); - expect(result).toEqual(true); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/labeler/register`, - expect.any(Object), - { params: expect.any(Object) }, - ); + expect(result).toBe(false); }); - it('should register labeler successfully without IP', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - email: 'test@example.com', - language: 'en', - country: 'US', - address: '0xabcdef1234567890', - }; + it('should not attach ip and register labeler', async () => { + const testEmail = faker.internet.email(); + const testEvmAddress = generateEthWallet().address; + const testCountry = faker.location.countryCode(); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 200, - }); - }); + mockHttpService.post.mockReturnValueOnce(createHttpServiceResponse(200)); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, + const result = await hcaptchaService.registerLabeler({ + email: testEmail, + evmAddress: testEvmAddress, + country: testCountry, }); - const result = await hcaptchaService.registerLabeler(mockData); + expect(result).toBe(true); + expect(mockHttpService.post).toHaveBeenCalledTimes(1); - expect(result).toEqual(true); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/labeler/register`, - expect.any(Object), - { params: expect.any(Object) }, + const [requestUrl, requestBodyArg, requestConfigArg] = + mockHttpService.post.mock.calls[0]; + + expect(requestUrl).toEqual(expect.any(String)); + expect(requestBodyArg).toEqual( + expect.objectContaining({ + email: testEmail, + eth_addr: testEvmAddress, + country: testCountry, + }), ); + expect(requestConfigArg).not.toHaveProperty('params.remoteip'); }); - it('should return false if API response status is not 200', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - ip: '127.0.0.1', - email: 'test@example.com', - language: 'en', - country: 'US', - address: '0xabcdef1234567890', - }; + it('should attach ip and register labeler', async () => { + const testEmail = faker.internet.email(); + const testEvmAddress = generateEthWallet().address; + const testIp = faker.internet.ip(); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 400, - }); - }); + mockHttpService.post.mockReturnValueOnce(createHttpServiceResponse(200)); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 400, + const result = await hcaptchaService.registerLabeler({ + email: testEmail, + evmAddress: testEvmAddress, + ip: testIp, }); - const result = await hcaptchaService.registerLabeler(mockData); + expect(result).toBe(true); - expect(result).toEqual(false); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/labeler/register`, - expect.any(Object), - { params: expect.any(Object) }, + expect(mockHttpService.post).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + email: testEmail, + eth_addr: testEvmAddress, + }), + { + params: expect.objectContaining({ + remoteip: testIp, + }), + }, ); }); - it('should return false if API request fails', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - ip: '127.0.0.1', - email: 'test@example.com', - language: 'en', - country: 'US', - address: '0xabcdef1234567890', - }; + it('should normalize evm address and register labeler', async () => { + const testEvmAddress = generateEthWallet().address.toLowerCase(); - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 401, - }); - }); + mockHttpService.post.mockReturnValueOnce(createHttpServiceResponse(200)); - (firstValueFrom as jest.Mock).mockRejectedValue( - new Error('Network Error'), - ); + const result = await hcaptchaService.registerLabeler({ + email: faker.internet.email(), + evmAddress: testEvmAddress, + }); - const result = await hcaptchaService.registerLabeler(mockData); + expect(result).toBe(true); - expect(result).toEqual(false); - expect(httpService.post).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/labeler/register`, + expect(mockHttpService.post).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + eth_addr: ethers.getAddress(testEvmAddress), + }), expect.any(Object), - { params: expect.any(Object) }, ); }); }); describe('getLabelerData', () => { - it('should retrieve labeler data successfully', async () => { - const mockData = { - email: 'test@example.com', - }; + it('should call configured get labeler URL', async () => { + await hcaptchaService.getLabelerData(faker.internet.email()); - const mockResponseData = { labelerData: 'data' }; - - httpService.get = jest.fn().mockImplementation(() => { - return of({ - status: 200, - data: mockResponseData, - }); - }); - - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - data: mockResponseData, - }); - - const result = await hcaptchaService.getLabelerData(mockData); - - expect(result).toEqual(mockResponseData); - expect(httpService.get).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/support/users`, + expect(mockHttpService.get).toHaveBeenCalledTimes(1); + expect(mockHttpService.get).toHaveBeenCalledWith( + `${mockHCaptchaConfigService.labelingURL}/support/users`, { - params: expect.any(Object), + params: expect.objectContaining({ + api_key: mockHCaptchaConfigService.apiKey, + }), }, ); }); - it('should return null if API response status is not 200', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - email: 'test@example.com', + it('should retrieve labeler data successfully', async () => { + const testEmail = faker.internet.email(); + + const response: LabelerData = { + sitekeys: [ + { + sitekey: faker.string.uuid(), + }, + ], }; - const mockResponseData = { labelerData: 'data' }; + mockHttpService.get.mockReturnValueOnce( + createHttpServiceResponse(200, response), + ); - httpService.get = jest.fn().mockImplementation(() => { - return of({ - status: 400, - data: mockResponseData, - }); - }); + const result = await hcaptchaService.getLabelerData(testEmail); - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 400, - data: mockResponseData, + expect(result).toBe(response); + expect(mockHttpService.get).toHaveBeenCalledTimes(1); + expect(mockHttpService.get).toHaveBeenCalledWith(expect.any(String), { + params: expect.objectContaining({ + email: testEmail, + }), }); + }); - const result = await hcaptchaService.getLabelerData(mockData); + it('returns null if API response status is not 200', async () => { + mockHttpService.get.mockReturnValueOnce( + createHttpServiceResponse(204, {}), + ); - expect(result).toEqual(null); - expect(httpService.get).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/support/users`, - { - params: expect.any(Object), - }, + const result = await hcaptchaService.getLabelerData( + faker.internet.email(), ); + + expect(result).toBe(null); }); it('should return null if API response does not contain data', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - email: 'test@example.com', - }; + mockHttpService.get.mockReturnValueOnce(createHttpServiceResponse(200)); - httpService.get = jest.fn().mockImplementation(() => { - return of({ - status: 200, - }); - }); - - (firstValueFrom as jest.Mock).mockResolvedValue({ - status: 200, - }); - - const result = await hcaptchaService.getLabelerData(mockData); - - expect(result).toEqual(null); - expect(httpService.get).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/support/users`, - { - params: expect.any(Object), - }, + const result = await hcaptchaService.getLabelerData( + faker.internet.email(), ); - }); - it('should return null if API request fails', async () => { - const mockData = { - url: 'https://example.com', - apiKey: 'api-key', - email: 'test@example.com', - }; - - httpService.post = jest.fn().mockImplementation(() => { - return of({ - status: 400, - }); - }); + expect(result).toBe(null); + }); - (firstValueFrom as jest.Mock).mockRejectedValue( - new Error('Network Error'), + it('returns null if API request fails', async () => { + mockHttpService.get.mockReturnValueOnce( + createHttpServiceRequestError(new Error()), ); - const result = await hcaptchaService.getLabelerData(mockData); - - expect(result).toEqual(null); - expect(httpService.get).toHaveBeenCalledWith( - `${MOCK_HCAPTCHA_LABELING_URL}/support/users`, - { - params: expect.any(Object), - }, + const result = await hcaptchaService.getLabelerData( + faker.internet.email(), ); + + expect(result).toBe(null); }); }); }); diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts index a2072f291f..ff8ffce082 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts @@ -1,28 +1,33 @@ import { HttpService } from '@nestjs/axios'; import { Injectable } from '@nestjs/common'; +import { ethers } from 'ethers'; import { firstValueFrom } from 'rxjs'; -import { - hCaptchaGetLabeler, - hCaptchaRegisterLabeler, - hCaptchaVerifyToken, -} from './hcaptcha.dto'; + import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; import logger from '../../logger'; +import { + GetLabelerQueryParams, + LabelerData, + RegisterLabelerBody, + RegisterLabelerData, + RegisterLabelerQueryParams, + SiteverifyQueryParams, + SiteverifyResponse, +} from './types'; + @Injectable() export class HCaptchaService { private readonly logger = logger.child({ context: HCaptchaService.name }); constructor( - private httpService: HttpService, + private readonly httpService: HttpService, private readonly hcaptchaConfigService: HCaptchaConfigService, ) {} - public async verifyToken(data: hCaptchaVerifyToken): Promise { + async verifyToken(token: string, ip?: string): Promise { try { - const { ip, token } = data; - - const queryParams: any = { + const queryParams: SiteverifyQueryParams = { secret: this.hcaptchaConfigService.secret, sitekey: this.hcaptchaConfigService.siteKey, response: token, @@ -33,7 +38,7 @@ export class HCaptchaService { } const response = await firstValueFrom( - this.httpService.post( + this.httpService.post( `${this.hcaptchaConfigService.protectionURL}/siteverify`, {}, { params: queryParams }, @@ -44,11 +49,13 @@ export class HCaptchaService { * WARN: Only this case is considered as "valid token" * since anything can change on hCaptcha side */ - if (response?.status === 200 && response.data.success === true) { + if (response.status === 200 && response.data?.success === true) { return true; - } else if (response?.data.success === false) { - this.logger.warn('Error occurred during token verification', { + } else if (response.data?.success === false) { + this.logger.warn('Error occurred during hCaptcha token verification', { errorCodes: response.data['error-codes'], + token, + ip, }); } } catch (error) { @@ -58,77 +65,85 @@ export class HCaptchaService { return false; } - /** - * Registers a user as a labeler at hCaptcha Foundation. - * @param {hCaptchaRegisterLabeler} data - The data required for user registration. - * @returns {Promise} - True if registration is successful, false otherwise. - */ - async registerLabeler(data: hCaptchaRegisterLabeler): Promise { - try { - const { ip, email, language, country, address } = data; + async registerLabeler(data: RegisterLabelerData): Promise { + const { email, evmAddress, country, ip } = data; + try { if (!country) { - this.logger.warn(`Country is not set for the user`); + this.logger.warn(`Country is not set for the user`, { + email, + evmAddress, + }); } - const queryParams: any = { + const queryParams: RegisterLabelerQueryParams = { api_key: this.hcaptchaConfigService.apiKey, - remoteip: ip || undefined, + }; + if (ip) { + queryParams.remoteip = ip; + } + + const body: RegisterLabelerBody = { + email, + eth_addr: ethers.getAddress(evmAddress), + language: this.hcaptchaConfigService.defaultLabelerLang, + country, }; const response = await firstValueFrom( - this.httpService.post( + this.httpService.post( `${this.hcaptchaConfigService.labelingURL}/labeler/register`, - { - email, - language, - country, - eth_addr: address, - }, + body, { params: queryParams, }, ), ); - if (response && response.status === 200) { + if (response.status === 200) { return true; + } else { + this.logger.warn('Non 200 response from labeling API', { + response: { + status: response.status, + data: response.data, + }, + ...data, + }); } } catch (error) { this.logger.error('Error occurred during labeling registration', { error, - userEmail: data.email, + ...data, }); } return false; } - /** - * Retrieves labeler data from hCaptcha Foundation. - * @param {hCaptchaGetLabeler} data - The data required to retrieve labeler data. - * @returns {Promise} - The labeler data. - */ - async getLabelerData(data: hCaptchaGetLabeler): Promise { - try { - const { email } = data; + async getLabelerData(email: string): Promise { + const queryParams: GetLabelerQueryParams = { + api_key: this.hcaptchaConfigService.apiKey, + email, + }; + try { const response = await firstValueFrom( - this.httpService.get( + this.httpService.get( `${this.hcaptchaConfigService.labelingURL}/support/users`, { - params: { api_key: this.hcaptchaConfigService.apiKey, email }, + params: queryParams, }, ), ); - if (response && response.data && response.status === 200) { + if (response.status === 200 && response.data) { return response.data; } } catch (error) { this.logger.error(`Error occurred while retrieving labeler data`, { error, - userEmail: data.email, + email, }); } diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/types.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/types.ts new file mode 100644 index 0000000000..ba02d050d8 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/types.ts @@ -0,0 +1,43 @@ +export type SiteverifyQueryParams = { + sitekey: string; + secret: string; + response: string; + remoteip?: string; +}; + +export type SiteverifyResponse = { + success?: boolean; + 'error-codes'?: string[]; +}; + +export type RegisterLabelerData = { + email: string; + evmAddress: string; + country?: string; + ip?: string; +}; + +type LabelingDefaultQueryParams = { + api_key: string; +}; + +export type RegisterLabelerQueryParams = LabelingDefaultQueryParams & { + remoteip?: string; +}; + +export type RegisterLabelerBody = { + email: string; + eth_addr: string; + language: string; + country?: string; +}; + +export type GetLabelerQueryParams = LabelingDefaultQueryParams & { + email: string; +}; + +export type LabelerData = { + sitekeys: Array<{ + sitekey: string; + }>; +}; diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts index b0c375f70a..b61c1a6d24 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts @@ -19,8 +19,8 @@ import { import { Public } from '../../common/decorators'; import { AuthService } from './auth.service'; import { JwtAuthGuard } from '../../common/guards'; -import { HCaptchaGuard } from '../../common/guards/hcaptcha'; import { RequestWithUser } from '../../common/interfaces/request'; +import { HCaptchaGuard } from '../../integrations/hcaptcha/hcaptcha.guard'; import { TokenRepository } from './token.repository'; import { TokenType } from './token.entity'; import { AuthControllerErrorsFilter } from './auth.error.filter'; diff --git a/packages/apps/reputation-oracle/server/src/common/guards/kyc-webhook.auth.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/common/guards/kyc-webhook.auth.ts rename to packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts index c03c992296..1c9e3cc10b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts @@ -20,7 +20,7 @@ import { JwtAuthGuard } from '../../common/guards'; import { RequestWithUser } from '../../common/interfaces/request'; import { KycSessionDto, KycSignedAddressDto, KycStatusDto } from './kyc.dto'; import { KycService } from './kyc.service'; -import { KycWebhookAuthGuard } from '../../common/guards/kyc-webhook.auth'; +import { KycWebhookAuthGuard } from './kyc-webhook-auth.guard'; import { KycErrorFilter } from './kyc.error.filter'; @ApiTags('KYC') diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts index e292483ff1..e44f86e600 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts @@ -18,10 +18,10 @@ import { } from '@nestjs/common'; import { Public } from '../../common/decorators'; import { JwtAuthGuard } from '../../common/guards'; -import { HCaptchaGuard } from '../../common/guards/hcaptcha'; import { SignatureType } from '../../common/enums/web3'; import { RequestWithUser } from '../../common/interfaces/request'; import { Web3ConfigService } from '../../config/web3-config.service'; +import { HCaptchaGuard } from '../../integrations/hcaptcha/hcaptcha.guard'; import { prepareSignatureBody } from '../../utils/web3'; import { DisableOperatorDto, diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index 4f64a506a2..0b48ae1446 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -1,7 +1,6 @@ import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; -import { ethers } from 'ethers'; import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; import { Web3ConfigService } from '../../config/web3-config.service'; import { @@ -118,22 +117,20 @@ export class UserService { // Register user as a labeler at hcaptcha foundation const registeredLabeler = await this.hcaptchaService.registerLabeler({ email: user.email, - language: this.hcaptchaConfigService.defaultLabelerLang, + evmAddress: user.evmAddress, country: user.kyc.country, - address: ethers.getAddress(user.evmAddress), }); if (!registeredLabeler) { throw new UserError(UserErrorMessage.LABELING_ENABLE_FAILED, user.id); } - // Retrieve labeler site key from hcaptcha foundation - const labelerData = await this.hcaptchaService.getLabelerData({ - email: user.email, - }); - if (!labelerData || !labelerData.sitekeys.length) { + // Retrieve labeler sitekeys from hCaptcha foundation + const labelerData = await this.hcaptchaService.getLabelerData(user.email); + if (!labelerData?.sitekeys.length) { throw new UserError(UserErrorMessage.LABELING_ENABLE_FAILED, user.id); } + const siteKey = labelerData.sitekeys[0].sitekey; const newSiteKey = new SiteKeyEntity(); diff --git a/packages/apps/reputation-oracle/server/test/mock-creators/nest.ts b/packages/apps/reputation-oracle/server/test/mock-creators/nest.ts index af49bedf8b..298fbd4b14 100644 --- a/packages/apps/reputation-oracle/server/test/mock-creators/nest.ts +++ b/packages/apps/reputation-oracle/server/test/mock-creators/nest.ts @@ -1,5 +1,7 @@ +import { HttpService } from '@nestjs/axios'; +import { AxiosResponse } from 'axios'; import { CallHandler, ExecutionContext } from '@nestjs/common'; -import { of } from 'rxjs'; +import { of, throwError } from 'rxjs'; class MockClassOfExecutionContext {} @@ -36,3 +38,28 @@ export function createCallHandlerMock(): CallHandlerMock { handle: jest.fn().mockReturnValue(of({})), }; } + +export function createHttpServiceResponse(statusCode: number, body?: T) { + return of({ + status: statusCode, + data: body, + } as AxiosResponse); +} + +export function createHttpServiceRequestError(error: Error) { + return throwError(() => error); +} + +export type HttpServiceMock = jest.Mocked>; +export function createHttpServiceMock( + defaultStatusCode = 501, +): HttpServiceMock { + return { + get: jest + .fn() + .mockReturnValue(createHttpServiceResponse(defaultStatusCode)), + post: jest + .fn() + .mockReturnValue(createHttpServiceResponse(defaultStatusCode)), + }; +} diff --git a/yarn.lock b/yarn.lock index 52c3f8670b..7679ce947e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3934,7 +3934,7 @@ uint8-varint "^2.0.1" uint8arrays "^5.0.0" -"@nestjs/axios@^3.1.2": +"@nestjs/axios@^3.1.2", "@nestjs/axios@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@nestjs/axios/-/axios-3.1.3.tgz#cf73f317f89800ec2f6f04b577677617c5aef0d9" integrity sha512-RZ/63c1tMxGLqyG3iOCVt7A72oy4x1eM6QEhd4KzCYpaVWW0igq0WSREeRoEZhIxRcZfDfIIkvsOMiM7yfVGZQ== @@ -8433,6 +8433,15 @@ axios@^1.1.3, axios@^1.3.4, axios@^1.4.0, axios@^1.6.7, axios@^1.7.2, axios@^1.7 form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.1.tgz#7c118d2146e9ebac512b7d1128771cdd738d11e3" + integrity sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" From a7f37fe7ffe338609b2e8507c593e3039e21864b Mon Sep 17 00:00:00 2001 From: mpblocky <185767042+mpblocky@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:07:13 +0100 Subject: [PATCH 23/27] [HUMAN APP] refactor: split sign up account type component (#3140) --- .../choose-sign-up-account-type.tsx | 160 +----------------- .../src/modules/homepage/components/index.ts | 3 + .../operator-sign-in.tsx} | 0 .../homepage/components/operator-sign-up.tsx | 85 ++++++++++ .../modules/homepage/components/welcome.tsx | 4 +- .../{worker-signin.tsx => worker-sign-in.tsx} | 0 .../homepage/components/worker-sign-up.tsx | 91 ++++++++++ .../frontend/src/modules/homepage/index.ts | 1 + .../src/modules/homepage/views/home.page.tsx | 17 +- .../src/modules/homepage/views/index.ts | 1 + .../human-app/frontend/src/router/routes.tsx | 2 +- 11 files changed, 191 insertions(+), 173 deletions(-) create mode 100644 packages/apps/human-app/frontend/src/modules/homepage/components/index.ts rename packages/apps/human-app/frontend/src/modules/homepage/{hooks/use-operator-signin.tsx => components/operator-sign-in.tsx} (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx rename packages/apps/human-app/frontend/src/modules/homepage/components/{worker-signin.tsx => worker-sign-in.tsx} (100%) create mode 100644 packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx create mode 100644 packages/apps/human-app/frontend/src/modules/homepage/index.ts create mode 100644 packages/apps/human-app/frontend/src/modules/homepage/views/index.ts diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx index c694ab548e..a0c2415e16 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx @@ -1,25 +1,18 @@ -import { Grid, List, ListItemText, Typography } from '@mui/material'; +import { Grid, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; -import { Button } from '@/shared/components/ui/button'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { routerPaths } from '@/router/router-paths'; import { PageCard } from '@/shared/components/ui/page-card'; import type { HomePageStageType } from '@/modules/homepage/views/home.page'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; import { useHomePageState } from '@/shared/contexts/homepage-state'; +import { WorkerSignUp } from './worker-sign-up'; +import { OperatorSignUp } from './operator-sign-up'; interface ChooseSignUpAccountType { setStage: (step: HomePageStageType) => void; } export function ChooseSignUpAccountType() { - const { colorPalette, isDarkMode } = useColorMode(); const { setPageView } = useHomePageState(); const { t } = useTranslation(); - const isMobile = useIsMobile('lg'); - const isMobileMd = useIsMobile('md'); const backToWelcomeStage = () => { setPageView('welcome'); @@ -30,7 +23,6 @@ export function ChooseSignUpAccountType() { backNavigation={backToWelcomeStage} cancelNavigation={backToWelcomeStage} childrenMaxWidth="876px" - showCancelButton={isMobileMd} maxContentWidth="748px" title={{t('homepage.welcome')} 👋} > @@ -38,150 +30,8 @@ export function ChooseSignUpAccountType() { {t('homepage.howWillUse')} - - -
- - {t('homepage.iWantToEarn')} - - - - - -
- - - -
-
- -
- - {t('homepage.joinAsOperator')} - - - - - -
- - - -
+ + ); diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts b/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts new file mode 100644 index 0000000000..c8bab39a3b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts @@ -0,0 +1,3 @@ +export * from './home-container'; +export * from './welcome'; +export * from './choose-sign-up-account-type'; diff --git a/packages/apps/human-app/frontend/src/modules/homepage/hooks/use-operator-signin.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-in.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/homepage/hooks/use-operator-signin.tsx rename to packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-in.tsx diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx new file mode 100644 index 0000000000..7ec945c879 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx @@ -0,0 +1,85 @@ +import { Grid, Typography, List, ListItemText, Button } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { routerPaths } from '@/router/router-paths'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; + +export function OperatorSignUp() { + const { colorPalette, isDarkMode } = useColorMode(); + const { t } = useTranslation(); + const isMobile = useIsMobile('lg'); + + return ( + +
+ + {t('homepage.joinAsOperator')} + + + + + +
+ + + +
+ ); +} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx index 5c844d81f0..23ad2f1c66 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx @@ -9,11 +9,11 @@ import { } from '@/shared/components/ui/icons'; import { Button } from '@/shared/components/ui/button'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { OperatorSignIn } from '@/modules/homepage/hooks/use-operator-signin'; -import { WorkerSignIn } from '@/modules/homepage/components/worker-signin'; +import { WorkerSignIn } from '@/modules/homepage/components/worker-sign-in'; import { useColorMode } from '@/shared/contexts/color-mode'; import { useHomePageState } from '@/shared/contexts/homepage-state'; import { useBackgroundContext } from '@/shared/contexts/background'; +import { OperatorSignIn } from './operator-sign-in'; export function Welcome() { const { colorPalette, isDarkMode } = useColorMode(); diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/worker-signin.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-in.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/homepage/components/worker-signin.tsx rename to packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-in.tsx diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx new file mode 100644 index 0000000000..8ec2dcab12 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx @@ -0,0 +1,91 @@ +import { Grid, Typography, List, ListItemText, Button } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { routerPaths } from '@/router/router-paths'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; + +export function WorkerSignUp() { + const { colorPalette, isDarkMode } = useColorMode(); + const { t } = useTranslation(); + const isMobile = useIsMobile('lg'); + + return ( + + +
+ + {t('homepage.iWantToEarn')} + + + + + +
+ + + +
+
+ ); +} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/index.ts b/packages/apps/human-app/frontend/src/modules/homepage/index.ts new file mode 100644 index 0000000000..7929623807 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/index.ts @@ -0,0 +1 @@ +export * from './views'; diff --git a/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx b/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx index 83821bd2ab..29d03570d6 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx @@ -1,23 +1,20 @@ import Box from '@mui/material/Box'; import { Paper } from '@mui/material'; import { Navigate } from 'react-router-dom'; -import { t } from 'i18next'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; import { useAuth } from '@/modules/auth/hooks/use-auth'; import { routerPaths } from '@/router/router-paths'; -import { Button } from '@/shared/components/ui/button'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { HomeContainer } from '@/modules/homepage/components/home-container'; +import { HomeContainer } from '@/modules/homepage/components'; import { useHomePageState } from '@/shared/contexts/homepage-state'; export type HomePageStageType = 'welcome' | 'chooseSignUpAccountType'; export function HomePage() { const { colorPalette, isDarkMode } = useColorMode(); - const { pageView, setPageView } = useHomePageState(); + const { pageView } = useHomePageState(); const isMobile = useIsMobile(); - const isMobileMd = useIsMobile('md'); const { user: worker } = useAuth(); const { user: operator } = useWeb3Auth(); @@ -54,16 +51,6 @@ export function HomePage() { }} > - {pageView === 'chooseSignUpAccountType' && !isMobileMd ? ( - - ) : null} ); diff --git a/packages/apps/human-app/frontend/src/modules/homepage/views/index.ts b/packages/apps/human-app/frontend/src/modules/homepage/views/index.ts new file mode 100644 index 0000000000..9879f4ab7b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/views/index.ts @@ -0,0 +1 @@ +export * from './home.page'; diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx index 0f07b20ec3..2f46ca7010 100644 --- a/packages/apps/human-app/frontend/src/router/routes.tsx +++ b/packages/apps/human-app/frontend/src/router/routes.tsx @@ -20,7 +20,6 @@ import { AddKeysOperatorPage } from '@/modules/operator/views/sign-up/add-keys.p import { AddStakeOperatorPage } from '@/modules/operator/views/sign-up/add-stake.page'; import { ConnectWalletOperatorPage } from '@/modules/operator/views/sign-up/connect-wallet.page'; import { Playground } from '@/modules/playground/views/playground.page'; -import { HomePage } from '@/modules/homepage/views/home.page'; import { HcaptchaLabelingPage, UserStatsAccordion, @@ -35,6 +34,7 @@ import { JobsDiscoveryPage } from '@/modules/worker/jobs-discovery'; import { WorkerProfilePage } from '@/modules/worker/profile'; import { SignUpWorkerPage } from '@/modules/signup/worker'; import { OperatorProfilePage } from '@/modules/operator/profile'; +import { HomePage } from '@/modules/homepage'; export const unprotectedRoutes: RouteProps[] = [ { From 1bf5802681287d09b54cdbea43c38f2cba99887d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:55:58 +0100 Subject: [PATCH 24/27] fix: update HMT price source to CoinLore API (#3152) --- .../src/common/config/env-config.service.ts | 8 ++-- .../server/src/modules/stats/stats.service.ts | 42 +++++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/packages/apps/dashboard/server/src/common/config/env-config.service.ts b/packages/apps/dashboard/server/src/common/config/env-config.service.ts index a7b5a6cb49..9946515770 100644 --- a/packages/apps/dashboard/server/src/common/config/env-config.service.ts +++ b/packages/apps/dashboard/server/src/common/config/env-config.service.ts @@ -5,7 +5,7 @@ const DEFAULT_CORS_ALLOWED_ORIGIN = 'http://localhost:3001'; const DEFAULT_CORS_ALLOWED_HEADERS = 'Content-Type, Accept'; const DEFAULT_HMT_PRICE_SOURCE = - 'https://api.coingecko.com/api/v3/simple/price?ids=human-protocol&vs_currencies=usd'; + 'https://api.coinlore.net/api/ticker/?id=53887'; const DEFAULT_HMT_PRICE_FROM = 'human-protocol'; const DEFAULT_HMT_PRICE_TO = 'usd'; const DEFAULT_HCAPTCHA_STATS_SOURCE = @@ -53,8 +53,10 @@ export class EnvironmentConfigService { DEFAULT_HMT_PRICE_SOURCE, ); } - get hmtPriceSourceApiKey(): string { - return this.configService.getOrThrow('HMT_PRICE_SOURCE_API_KEY'); + get hmtPriceSourceApiKey(): string | undefined { + return this.configService.get( + 'HMT_PRICE_SOURCE_API_KEY', + ); } get hmtPriceFromKey(): string { return this.configService.get( diff --git a/packages/apps/dashboard/server/src/modules/stats/stats.service.ts b/packages/apps/dashboard/server/src/modules/stats/stats.service.ts index 041747a743..c81953baee 100644 --- a/packages/apps/dashboard/server/src/modules/stats/stats.service.ts +++ b/packages/apps/dashboard/server/src/modules/stats/stats.service.ts @@ -321,22 +321,46 @@ export class StatsService implements OnModuleInit { return cachedHmtPrice; } + const headers = this.envConfigService.hmtPriceSourceApiKey + ? { 'x-cg-demo-api-key': this.envConfigService.hmtPriceSourceApiKey } + : {}; + const { data } = await lastValueFrom( - this.httpService.get(this.envConfigService.hmtPriceSource, { - headers: { - 'x-cg-demo-api-key': this.envConfigService.hmtPriceSourceApiKey, - }, - }), + this.httpService.get(this.envConfigService.hmtPriceSource, { headers }), ); - const hmtPrice = - data[this.envConfigService.hmtPriceFromKey][ - this.envConfigService.hmtPriceToKey - ]; + + let hmtPrice: number; + + if (this.envConfigService.hmtPriceSource.includes('coingecko')) { + if ( + !data || + !data[this.envConfigService.hmtPriceFromKey] || + !data[this.envConfigService.hmtPriceFromKey][ + this.envConfigService.hmtPriceToKey + ] + ) { + throw new Error('Failed to fetch HMT price from CoinGecko API'); + } + hmtPrice = parseFloat( + data[this.envConfigService.hmtPriceFromKey][ + this.envConfigService.hmtPriceToKey + ], + ); + } else if (this.envConfigService.hmtPriceSource.includes('coinlore')) { + if (!data || !data[0] || !data[0].price_usd) { + throw new Error('Failed to fetch HMT price from Coinlore API'); + } + hmtPrice = parseFloat(data[0].price_usd); + } else { + throw new Error('Unsupported HMT price source'); + } + await this.cacheManager.set( this.redisConfigService.hmtPriceCacheKey, hmtPrice, this.redisConfigService.cacheHmtPriceTTL, ); + return hmtPrice; } From 31f56bebe70d57eb6a1596bd2957e6d605bab0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:03:31 +0100 Subject: [PATCH 25/27] fix: validate HMT symbol in Coinlore API response (#3156) --- .../apps/dashboard/server/src/modules/stats/stats.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apps/dashboard/server/src/modules/stats/stats.service.ts b/packages/apps/dashboard/server/src/modules/stats/stats.service.ts index c81953baee..c40076db3b 100644 --- a/packages/apps/dashboard/server/src/modules/stats/stats.service.ts +++ b/packages/apps/dashboard/server/src/modules/stats/stats.service.ts @@ -347,7 +347,7 @@ export class StatsService implements OnModuleInit { ], ); } else if (this.envConfigService.hmtPriceSource.includes('coinlore')) { - if (!data || !data[0] || !data[0].price_usd) { + if (!data || !data[0] || !data[0].price_usd || data[0].symbol !== 'HMT') { throw new Error('Failed to fetch HMT price from Coinlore API'); } hmtPrice = parseFloat(data[0].price_usd); From b2210030a762eb5f865d273dc759c4b194d275f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:23:20 +0100 Subject: [PATCH 26/27] feat: add RateModule and RateService for currency rate management (#3153) --- .../src/modules/cron-job/cron-job.service.spec.ts | 2 +- .../server/src/modules/job/job.module.ts | 2 ++ .../server/src/modules/job/job.service.spec.ts | 2 +- .../server/src/modules/job/job.service.ts | 2 +- .../src/modules/payment/payment.controller.ts | 2 +- .../server/src/modules/payment/payment.module.ts | 13 ++++--------- .../src/modules/payment/payment.service.spec.ts | 2 +- .../server/src/modules/payment/payment.service.ts | 2 +- .../server/src/modules/rate/rate.module.ts | 11 +++++++++++ .../modules/{payment => rate}/rate.service.spec.ts | 0 .../src/modules/{payment => rate}/rate.service.ts | 0 11 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 packages/apps/job-launcher/server/src/modules/rate/rate.module.ts rename packages/apps/job-launcher/server/src/modules/{payment => rate}/rate.service.spec.ts (100%) rename packages/apps/job-launcher/server/src/modules/{payment => rate}/rate.service.ts (100%) diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts index 05ab5b1cf5..ba06a837c0 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts @@ -49,7 +49,7 @@ import { PGPConfigService } from '../../common/config/pgp-config.service'; import { ErrorCronJob } from '../../common/constants/errors'; import { ControlledError } from '../../common/errors/controlled'; import { HttpStatus } from '@nestjs/common'; -import { RateService } from '../payment/rate.service'; +import { RateService } from '../rate/rate.service'; import { StatusEvent } from '@human-protocol/sdk/dist/graphql'; import { ethers } from 'ethers'; import { NetworkConfigService } from '../../common/config/network-config.service'; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.module.ts b/packages/apps/job-launcher/server/src/modules/job/job.module.ts index 71795c5100..ef57461d8d 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.module.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.module.ts @@ -18,6 +18,7 @@ import { MutexManagerService } from '../mutex/mutex-manager.service'; import { QualificationModule } from '../qualification/qualification.module'; import { WhitelistModule } from '../whitelist/whitelist.module'; import { RoutingProtocolModule } from '../routing-protocol/routing-protocol.module'; +import { RateModule } from '../rate/rate.module'; @Module({ imports: [ @@ -32,6 +33,7 @@ import { RoutingProtocolModule } from '../routing-protocol/routing-protocol.modu QualificationModule, WhitelistModule, RoutingProtocolModule, + RateModule, ], controllers: [JobController], providers: [ diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 9b1fbc2bec..97e336cfa9 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -30,7 +30,7 @@ import { AuthConfigService } from '../../common/config/auth-config.service'; import { Web3ConfigService } from '../../common/config/web3-config.service'; import { CvatConfigService } from '../../common/config/cvat-config.service'; import { PGPConfigService } from '../../common/config/pgp-config.service'; -import { RateService } from '../payment/rate.service'; +import { RateService } from '../rate/rate.service'; import { QualificationService } from '../qualification/qualification.service'; import { WhitelistService } from '../whitelist/whitelist.service'; import { generateRandomEthAddress } from '../../../test/utils/address'; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 119c91378b..fb813d1b8a 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -113,7 +113,7 @@ import { import { WebhookEntity } from '../webhook/webhook.entity'; import { WebhookRepository } from '../webhook/webhook.repository'; import { ControlledError } from '../../common/errors/controlled'; -import { RateService } from '../payment/rate.service'; +import { RateService } from '../rate/rate.service'; import { PageDto } from '../../common/pagination/pagination.dto'; import { CronJobType } from '../../common/enums/cron-job'; import { CronJobRepository } from '../cron-job/cron-job.repository'; diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts index cba42890e0..8ca5968ed4 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts @@ -38,7 +38,7 @@ import { PaymentService } from './payment.service'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; import { ControlledError } from '../../common/errors/controlled'; import { ServerConfigService } from '../../common/config/server-config.service'; -import { RateService } from './rate.service'; +import { RateService } from '../rate/rate.service'; import { PageDto } from '../../common/pagination/pagination.dto'; import { WhitelistAuthGuard } from '../../common/guards/whitelist.auth'; import { Web3Env } from '../../common/enums/web3'; diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.module.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.module.ts index bbf2d841cc..6667226e1f 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.module.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.module.ts @@ -9,12 +9,12 @@ import { PaymentController } from './payment.controller'; import { PaymentRepository } from './payment.repository'; import { HttpModule } from '@nestjs/axios'; import { Web3Module } from '../web3/web3.module'; -import { RateService } from './rate.service'; import { WhitelistModule } from '../whitelist/whitelist.module'; import { JobEntity } from '../job/job.entity'; import { UserEntity } from '../user/user.entity'; import { JobRepository } from '../job/job.repository'; import { UserRepository } from '../user/user.repository'; +import { RateModule } from '../rate/rate.module'; @Module({ imports: [ @@ -23,6 +23,7 @@ import { UserRepository } from '../user/user.repository'; ConfigModule, Web3Module, WhitelistModule, + RateModule, MinioModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], @@ -38,13 +39,7 @@ import { UserRepository } from '../user/user.repository'; }), ], controllers: [PaymentController], - providers: [ - PaymentService, - PaymentRepository, - RateService, - JobRepository, - UserRepository, - ], - exports: [PaymentService, PaymentRepository, RateService], + providers: [PaymentService, PaymentRepository, JobRepository, UserRepository], + exports: [PaymentService, PaymentRepository], }) export class PaymentModule {} diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts index 8259786fee..a7b4adb0c3 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts @@ -39,7 +39,7 @@ import { DatabaseError } from '../../common/errors/database'; import { StripeConfigService } from '../../common/config/stripe-config.service'; import { NetworkConfigService } from '../../common/config/network-config.service'; import { ControlledError } from '../../common/errors/controlled'; -import { RateService } from './rate.service'; +import { RateService } from '../rate/rate.service'; import { UserRepository } from '../user/user.repository'; import { JobRepository } from '../job/job.repository'; import { GetPaymentsDto } from './payment.dto'; diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts index 251b7497b9..df540f41a3 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts @@ -37,7 +37,7 @@ import { div, eq, mul, add } from '../../common/utils/decimal'; import { verifySignature } from '../../common/utils/signature'; import { PaymentEntity } from './payment.entity'; import { ControlledError } from '../../common/errors/controlled'; -import { RateService } from './rate.service'; +import { RateService } from '../rate/rate.service'; import { UserEntity } from '../user/user.entity'; import { UserRepository } from '../user/user.repository'; import { JobRepository } from '../job/job.repository'; diff --git a/packages/apps/job-launcher/server/src/modules/rate/rate.module.ts b/packages/apps/job-launcher/server/src/modules/rate/rate.module.ts new file mode 100644 index 0000000000..5a37bf958b --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/rate/rate.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { RateService } from './rate.service'; +import { ConfigModule } from '@nestjs/config'; +import { HttpModule } from '@nestjs/axios'; + +@Module({ + imports: [HttpModule, ConfigModule], + providers: [RateService], + exports: [RateService], +}) +export class RateModule {} diff --git a/packages/apps/job-launcher/server/src/modules/payment/rate.service.spec.ts b/packages/apps/job-launcher/server/src/modules/rate/rate.service.spec.ts similarity index 100% rename from packages/apps/job-launcher/server/src/modules/payment/rate.service.spec.ts rename to packages/apps/job-launcher/server/src/modules/rate/rate.service.spec.ts diff --git a/packages/apps/job-launcher/server/src/modules/payment/rate.service.ts b/packages/apps/job-launcher/server/src/modules/rate/rate.service.ts similarity index 100% rename from packages/apps/job-launcher/server/src/modules/payment/rate.service.ts rename to packages/apps/job-launcher/server/src/modules/rate/rate.service.ts From 64de1ac5ddf6304c0c68054e261d85bb50f2d467 Mon Sep 17 00:00:00 2001 From: Dmitry Nechay Date: Wed, 5 Mar 2025 19:11:27 +0300 Subject: [PATCH 27/27] [Reputation Oracle] refactor: typeorm usage (#3154) * fix: db entities usage bypassing repository * refactor: use typeorm Data Mapper instead of Active Record * refactor: cleanup typeorm configs * fix: entity schema out of datasource --- .../reputation-oracle/server/package.json | 8 +-- .../server/src/common/constants/index.ts | 2 +- .../server/src/database/base.entity.ts | 13 +++-- .../server/src/database/database.module.ts | 49 +++++++++---------- .../1694691302615-InitialMigration.ts | 6 +-- .../server/src/modules/auth/auth.service.ts | 2 +- .../server/src/modules/auth/token.entity.ts | 4 +- .../src/modules/auth/token.repository.ts | 12 ++--- .../src/modules/cron-job/cron-job.entity.ts | 4 +- .../escrow-completion.entity.ts | 4 +- .../escrow-payouts-batch.entity.ts | 4 +- .../server/src/modules/kyc/kyc.entity.ts | 4 +- .../qualification/qualification.entity.ts | 4 +- .../user-qualification.entity.ts | 4 +- .../modules/reputation/reputation.entity.ts | 4 +- .../reputation/reputation.service.spec.ts | 9 ++-- .../modules/reputation/reputation.service.ts | 14 +++--- .../src/modules/user/site-key.entity.ts | 4 +- .../server/src/modules/user/user.entity.ts | 4 +- .../src/modules/user/user.service.spec.ts | 9 ---- .../webhook/webhook-incoming.entity.ts | 4 +- .../webhook/webhook-outgoing.entity.ts | 4 +- ...ig.ts => typeorm-migrations-datasource.ts} | 25 ++++++---- 23 files changed, 94 insertions(+), 103 deletions(-) rename packages/apps/reputation-oracle/server/{typeorm.config.ts => typeorm-migrations-datasource.ts} (65%) diff --git a/packages/apps/reputation-oracle/server/package.json b/packages/apps/reputation-oracle/server/package.json index 76ab5cd62b..e50ed8be30 100644 --- a/packages/apps/reputation-oracle/server/package.json +++ b/packages/apps/reputation-oracle/server/package.json @@ -13,10 +13,10 @@ "start:debug": "nest start --debug --watch", "start:prod": "node dist/src/main", "migration:create": "yarn build && typeorm-ts-node-commonjs migration:create", - "migration:generate": "yarn build && typeorm-ts-node-commonjs migration:generate -d typeorm.config.ts", - "migration:revert": "yarn build && typeorm-ts-node-commonjs migration:revert -d typeorm.config.ts", - "migration:run": "yarn build && typeorm-ts-node-commonjs migration:run -d typeorm.config.ts", - "migration:show": "yarn build && typeorm-ts-node-commonjs migration:show -d typeorm.config.ts", + "migration:generate": "yarn build && typeorm-ts-node-commonjs migration:generate -d typeorm-migrations-datasource.ts", + "migration:revert": "yarn build && typeorm-ts-node-commonjs migration:revert -d typeorm-migrations-datasource.ts", + "migration:run": "yarn build && typeorm-ts-node-commonjs migration:run -d typeorm-migrations-datasource.ts", + "migration:show": "yarn build && typeorm-ts-node-commonjs migration:show -d typeorm-migrations-datasource.ts", "docker:db:up": "docker compose up -d postgres && yarn migration:run", "docker:db:down": "docker compose down postgres", "setup:local": "ts-node ./test/setup.ts", diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index 969a25dc71..2d1b98eeb5 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -1,4 +1,4 @@ -export const NS = 'hmt'; +export const DATABASE_SCHEMA_NAME = 'hmt'; export const INITIAL_REPUTATION = 0; export const JWT_STRATEGY_NAME = 'jwt-http'; diff --git a/packages/apps/reputation-oracle/server/src/database/base.entity.ts b/packages/apps/reputation-oracle/server/src/database/base.entity.ts index 963505429c..0779d61674 100644 --- a/packages/apps/reputation-oracle/server/src/database/base.entity.ts +++ b/packages/apps/reputation-oracle/server/src/database/base.entity.ts @@ -1,30 +1,29 @@ import { - BaseEntity as OrmBaseEntity, BeforeInsert, BeforeUpdate, Column, PrimaryGeneratedColumn, } from 'typeorm'; -export abstract class BaseEntity extends OrmBaseEntity { +export abstract class BaseEntity { @PrimaryGeneratedColumn() - public id: number; + id: number; @Column({ type: 'timestamptz' }) - public createdAt: Date; + createdAt: Date; @Column({ type: 'timestamptz' }) - public updatedAt: Date; + updatedAt: Date; @BeforeInsert() - public beforeInsert(): void { + protected beforeInsert(): void { const date = new Date(); this.createdAt = date; this.updatedAt = date; } @BeforeUpdate() - public beforeUpdate(): void { + protected beforeUpdate(): void { const date = new Date(); this.updatedAt = date; } diff --git a/packages/apps/reputation-oracle/server/src/database/database.module.ts b/packages/apps/reputation-oracle/server/src/database/database.module.ts index 029029a24a..8db906b095 100644 --- a/packages/apps/reputation-oracle/server/src/database/database.module.ts +++ b/packages/apps/reputation-oracle/server/src/database/database.module.ts @@ -1,10 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import * as path from 'path'; import { LoggerOptions } from 'typeorm'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; -import { NS } from '../common/constants'; import { ReputationEntity } from '../modules/reputation/reputation.entity'; import { TokenEntity } from '../modules/auth/token.entity'; import { UserEntity } from '../modules/user/user.entity'; @@ -18,7 +16,6 @@ import { WebhookIncomingEntity } from '../modules/webhook/webhook-incoming.entit import { WebhookOutgoingEntity } from '../modules/webhook/webhook-outgoing.entity'; import { EscrowCompletionEntity } from '../modules/escrow-completion/escrow-completion.entity'; import { EscrowPayoutsBatchEntity } from '../modules/escrow-completion/escrow-payouts-batch.entity'; -import Environment from '../utils/environment'; import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; @@ -37,9 +34,31 @@ import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; ? 'all' : ((loggerOptions as LoggerOptions) ?? false), ); + return { - name: 'default', + name: 'default-connection', type: 'postgres', + + ...(databaseConfigService.url + ? { + url: databaseConfigService.url, + } + : { + host: databaseConfigService.host, + port: databaseConfigService.port, + username: databaseConfigService.user, + password: databaseConfigService.password, + database: databaseConfigService.database, + }), + ssl: databaseConfigService.ssl, + + namingStrategy: new SnakeNamingStrategy(), + /** + * Schema synchronization should be done + * via manually running migrations + */ + synchronize: false, + migrationsRun: false, entities: [ WebhookIncomingEntity, WebhookOutgoingEntity, @@ -54,29 +73,9 @@ import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; QualificationEntity, UserQualificationEntity, ], - // We are using migrations, synchronize should be set to false. - synchronize: false, - // Run migrations automatically, - // you can disable this if you prefer running migration manually. - migrationsTableName: NS, - migrationsTransactionMode: 'each', - namingStrategy: new SnakeNamingStrategy(), + logging: true, - // Allow both start:prod and start:dev to use migrations - // __dirname is either dist or server folder, meaning either - // the compiled js in prod or the ts in dev. - migrations: [path.join(__dirname, '/migrations/**/*{.ts,.js}')], - //"migrations": ["dist/migrations/*{.ts,.js}"], logger: typeOrmLoggerService, - url: databaseConfigService.url, - host: databaseConfigService.host, - port: databaseConfigService.port, - username: databaseConfigService.user, - password: databaseConfigService.password, - database: databaseConfigService.database, - keepConnectionAlive: Environment.isTest(), - migrationsRun: false, - ssl: databaseConfigService.ssl, }; }, }), diff --git a/packages/apps/reputation-oracle/server/src/database/migrations/1694691302615-InitialMigration.ts b/packages/apps/reputation-oracle/server/src/database/migrations/1694691302615-InitialMigration.ts index 11d942a36d..6401a7d909 100644 --- a/packages/apps/reputation-oracle/server/src/database/migrations/1694691302615-InitialMigration.ts +++ b/packages/apps/reputation-oracle/server/src/database/migrations/1694691302615-InitialMigration.ts @@ -1,11 +1,11 @@ -import { NS } from '../../common/constants'; import { MigrationInterface, QueryRunner } from 'typeorm'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; export class InitialMigration1694776006387 implements MigrationInterface { name = 'InitialMigration1694776006387'; public async up(queryRunner: QueryRunner): Promise { - await queryRunner.createSchema(NS, true); + await queryRunner.createSchema(DATABASE_SCHEMA_NAME, true); await queryRunner.query( `CREATE TYPE "hmt"."tokens_token_type_enum" AS ENUM('EMAIL', 'PASSWORD')`, ); @@ -61,6 +61,6 @@ export class InitialMigration1694776006387 implements MigrationInterface { await queryRunner.query(`DROP TYPE "hmt"."users_type_enum"`); await queryRunner.query(`DROP TABLE "hmt"."tokens"`); await queryRunner.query(`DROP TYPE "hmt"."tokens_token_type_enum"`); - await queryRunner.dropSchema(NS); + await queryRunner.dropSchema(DATABASE_SCHEMA_NAME); } } diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts index 72f79a40c4..59c4922fcd 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts @@ -278,7 +278,7 @@ export class AuthService { ); if (existingToken) { - await existingToken.remove(); + await this.tokenRepository.deleteOne(existingToken); } const tokenEntity = new TokenEntity(); diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts b/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts index 964544f555..04e536e5c6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts @@ -9,7 +9,7 @@ import { import { UserEntity } from '../user/user.entity'; import { BaseEntity } from '../../database/base.entity'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { IBase } from '../../common/interfaces/base'; export enum TokenType { @@ -23,7 +23,7 @@ export interface IToken extends IBase { type: TokenType; } -@Entity({ schema: NS, name: 'tokens' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'tokens' }) @Index(['type', 'userId'], { unique: true }) export class TokenEntity extends BaseEntity implements IToken { @Column({ type: 'uuid', unique: true }) diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts b/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts index 8157a1c0ad..8d2ff80e75 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { BaseRepository } from '../../database/base.repository'; -import { DataSource, DeleteResult } from 'typeorm'; +import { DataSource } from 'typeorm'; import { TokenEntity, TokenType } from './token.entity'; @Injectable() @@ -9,7 +9,7 @@ export class TokenRepository extends BaseRepository { super(TokenEntity, dataSource); } - public async findOneByUuidAndType( + async findOneByUuidAndType( uuid: string, type: TokenType, ): Promise { @@ -22,7 +22,7 @@ export class TokenRepository extends BaseRepository { }); } - public async findOneByUserIdAndType( + async findOneByUserIdAndType( userId: number, type: TokenType, ): Promise { @@ -35,10 +35,10 @@ export class TokenRepository extends BaseRepository { }); } - public async deleteOneByTypeAndUserId( + async deleteOneByTypeAndUserId( type: TokenType, userId: number, - ): Promise { - return this.delete({ type, userId }); + ): Promise { + await this.delete({ type, userId }); } } diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts index 9e057ee7d6..9c6ee39929 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts @@ -1,11 +1,11 @@ import { BeforeInsert, Column, Entity, Index } from 'typeorm'; -import { NS } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { ICronJob } from '../../common/interfaces/cron-job'; import { CronJobType } from '../../common/enums/cron-job'; -@Entity({ schema: NS, name: 'cron-jobs' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'cron-jobs' }) @Index(['cronJobType'], { unique: true }) export class CronJobEntity extends BaseEntity implements ICronJob { @Column({ diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts index a1eaa004c1..6e86e2556b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts @@ -1,11 +1,11 @@ import { Column, Entity, Index } from 'typeorm'; -import { NS } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { EscrowCompletionStatus } from '../../common/enums'; import { ChainId } from '@human-protocol/sdk'; -@Entity({ schema: NS, name: 'escrow_completion_tracking' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'escrow_completion_tracking' }) @Index(['escrowAddress', 'chainId'], { unique: true }) export class EscrowCompletionEntity extends BaseEntity { @Column({ type: 'int' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts index 120be9fb7b..8b2cd88e22 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index, ManyToOne } from 'typeorm'; -import { NS } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import type { EscrowCompletionEntity } from './escrow-completion.entity'; export type EscrowPayout = { @@ -9,7 +9,7 @@ export type EscrowPayout = { amount: string; }; -@Entity({ schema: NS, name: 'escrow_payouts_batch' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'escrow_payouts_batch' }) @Index(['escrowCompletionTrackingId', 'payoutsHash'], { unique: true }) export class EscrowPayoutsBatchEntity extends BaseEntity { @ManyToOne('EscrowCompletionEntity', { onDelete: 'CASCADE' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts index 3ee4b406a2..b8875aa5b1 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts @@ -1,11 +1,11 @@ import { Column, Entity, JoinColumn, OneToOne } from 'typeorm'; -import { NS } from '../../common/constants'; import { KycStatus } from '../../common/enums/user'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { UserEntity } from '../user/user.entity'; -@Entity({ schema: NS, name: 'kycs' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'kycs' }) export class KycEntity extends BaseEntity { @Column({ type: 'varchar', unique: true, primary: true }) public sessionId: string; diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts index 272b4f8d18..0c0f793b9a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts @@ -1,9 +1,9 @@ import { Column, Entity, Index, OneToMany } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { UserQualificationEntity } from './user-qualification.entity'; -@Entity({ schema: NS, name: 'qualifications' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'qualifications' }) @Index(['reference'], { unique: true }) export class QualificationEntity extends BaseEntity { @Column({ type: 'varchar', unique: true }) diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts index acb5f3282e..d7bf259a58 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts @@ -1,10 +1,10 @@ import { Entity, ManyToOne } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { UserEntity } from '../user/user.entity'; import { QualificationEntity } from '../qualification/qualification.entity'; -@Entity({ schema: NS, name: 'user_qualifications' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'user_qualifications' }) export class UserQualificationEntity extends BaseEntity { @ManyToOne(() => UserEntity, (user) => user.userQualifications) public user: UserEntity; diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts index 4b81975c05..bf36b93e97 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts @@ -1,10 +1,10 @@ import { Column, Entity } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { ReputationEntityType } from '../../common/enums'; -@Entity({ schema: NS, name: 'reputation' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'reputation' }) export class ReputationEntity extends BaseEntity { @Column({ type: 'int' }) public chainId: number; diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts index edf9b612d9..b1a49d865f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts @@ -313,7 +313,6 @@ describe('ReputationService', () => { address, reputationPoints: 1, type: ReputationEntityType.RECORDING_ORACLE, - save: jest.fn(), }; jest @@ -326,7 +325,7 @@ describe('ReputationService', () => { address, ); expect(reputationEntity.reputationPoints).toBe(2); - expect(reputationEntity.save).toHaveBeenCalled(); + expect(reputationRepository.updateOne).toHaveBeenCalled(); }); }); @@ -359,7 +358,6 @@ describe('ReputationService', () => { address, reputationPoints: 1, type: ReputationEntityType.RECORDING_ORACLE, - save: jest.fn(), }; jest @@ -372,7 +370,7 @@ describe('ReputationService', () => { address, ); expect(reputationEntity.reputationPoints).toBe(0); - expect(reputationEntity.save).toHaveBeenCalled(); + expect(reputationRepository.updateOne).toHaveBeenCalled(); }); it('should return if called for Reputation Oracle itself', async () => { @@ -380,7 +378,6 @@ describe('ReputationService', () => { address, reputationPoints: 701, type: ReputationEntityType.RECORDING_ORACLE, - save: jest.fn(), }; jest @@ -397,7 +394,7 @@ describe('ReputationService', () => { address, ); expect(reputationEntity.reputationPoints).toBe(701); - expect(reputationEntity.save).toHaveBeenCalledTimes(0); + expect(reputationRepository.updateOne).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts index 76bec58b85..b658887ea9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts @@ -258,10 +258,9 @@ export class ReputationService { return; } - Object.assign(reputationEntity, { - reputationPoints: reputationEntity.reputationPoints + 1, - }); - reputationEntity.save(); + reputationEntity.reputationPoints += 1; + + await this.reputationRepository.updateOne(reputationEntity); } /** @@ -301,10 +300,9 @@ export class ReputationService { return; } - Object.assign(reputationEntity, { - reputationPoints: reputationEntity.reputationPoints - 1, - }); - reputationEntity.save(); + reputationEntity.reputationPoints -= 1; + + await this.reputationRepository.updateOne(reputationEntity); } /** diff --git a/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts b/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts index 5d23899def..148220fbc8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts @@ -1,5 +1,5 @@ import { Entity, Column, JoinColumn, ManyToOne, Unique } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { UserEntity } from './user.entity'; import { SiteKeyType } from '../../common/enums'; @@ -7,7 +7,7 @@ import { SiteKeyType } from '../../common/enums'; // TypeORM doesn't natively support partial unique indices. // To ensure a user can only have one record in the site_keys table with the type 'hcaptcha', // we enforce a partial unique index at the database level. -@Entity({ schema: NS, name: 'site_keys' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'site_keys' }) @Unique(['siteKey', 'type', 'user']) export class SiteKeyEntity extends BaseEntity { @Column({ type: 'varchar' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts index 285d3b3df8..bd52527fb8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts @@ -1,7 +1,7 @@ import { Exclude } from 'class-transformer'; import { Column, Entity, OneToMany, OneToOne } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { UserStatus, Role } from '../../common/enums/user'; import { IUser } from '../../common/interfaces/user'; import { BaseEntity } from '../../database/base.entity'; @@ -10,7 +10,7 @@ import { KycEntity } from '../kyc/kyc.entity'; import { SiteKeyEntity } from './site-key.entity'; import { UserQualificationEntity } from '../qualification/user-qualification.entity'; -@Entity({ schema: NS, name: 'users' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'users' }) export class UserEntity extends BaseEntity implements IUser { @Exclude() @Column({ type: 'varchar', nullable: true }) diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index ea2e25965c..83abc43150 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -162,7 +162,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; const mockLabelerData = { sitekeys: [{ sitekey: 'site_key' }] }; @@ -193,7 +192,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; await expect( @@ -213,7 +211,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.REVIEW, }, - save: jest.fn(), }; await expect( @@ -242,7 +239,6 @@ describe('UserService', () => { status: KycStatus.APPROVED, }, siteKeys: [siteKeyEntity], - save: jest.fn(), }; hcaptchaService.registerLabeler = jest.fn(); @@ -265,7 +261,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; hcaptchaService.registerLabeler = jest.fn().mockResolvedValueOnce(false); @@ -290,7 +285,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; hcaptchaService.registerLabeler = jest.fn().mockResolvedValueOnce(true); @@ -315,7 +309,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; hcaptchaService.registerLabeler = jest.fn().mockResolvedValueOnce(false); @@ -348,7 +341,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; const signature = await signMessage( @@ -485,7 +477,6 @@ describe('UserService', () => { country: 'FR', status: KycStatus.APPROVED, }, - save: jest.fn(), }; const address = '0x123'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts index a95a28da48..25c669e2df 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts @@ -1,11 +1,11 @@ import { Column, Entity, Index } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { WebhookIncomingStatus } from '../../common/enums'; import { ChainId } from '@human-protocol/sdk'; -@Entity({ schema: NS, name: 'webhook_incoming' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'webhook_incoming' }) @Index(['chainId', 'escrowAddress'], { unique: true }) export class WebhookIncomingEntity extends BaseEntity { @Column({ type: 'int' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts index 086e94be0f..743825888d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts @@ -1,10 +1,10 @@ import { Column, Entity, Index } from 'typeorm'; -import { NS } from '../../common/constants'; +import { DATABASE_SCHEMA_NAME } from '../../common/constants'; import { BaseEntity } from '../../database/base.entity'; import { WebhookOutgoingStatus } from '../../common/enums'; -@Entity({ schema: NS, name: 'webhook_outgoing' }) +@Entity({ schema: DATABASE_SCHEMA_NAME, name: 'webhook_outgoing' }) @Index(['hash'], { unique: true }) export class WebhookOutgoingEntity extends BaseEntity { @Column({ type: 'jsonb' }) diff --git a/packages/apps/reputation-oracle/server/typeorm.config.ts b/packages/apps/reputation-oracle/server/typeorm-migrations-datasource.ts similarity index 65% rename from packages/apps/reputation-oracle/server/typeorm.config.ts rename to packages/apps/reputation-oracle/server/typeorm-migrations-datasource.ts index d63d544043..7c08b41eed 100644 --- a/packages/apps/reputation-oracle/server/typeorm.config.ts +++ b/packages/apps/reputation-oracle/server/typeorm-migrations-datasource.ts @@ -10,19 +10,26 @@ dotenv.config({ path: [`.env.${Environment.name}`, '.env'], }); +const connectionUrl = process.env.POSTGRES_URL; + export default new DataSource({ type: 'postgres', - url: process.env.POSTGRES_URL, - host: process.env.POSTGRES_HOST, - port: Number(process.env.POSTGRES_PORT), - username: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD, - database: process.env.POSTGRES_DATABASE, - entities: ['dist/src/**/*.entity{.ts,.js}'], + ...(connectionUrl + ? { + url: connectionUrl, + } + : { + host: process.env.POSTGRES_HOST, + port: Number(process.env.POSTGRES_PORT), + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + database: process.env.POSTGRES_DATABASE, + }), + ssl: process.env.POSTGRES_SSL?.toLowerCase() === 'true', synchronize: false, + migrationsRun: true, migrations: ['dist/src/database/migrations/*{.ts,.js}'], migrationsTableName: 'migrations_typeorm', - migrationsRun: true, namingStrategy: new SnakeNamingStrategy(), - ssl: process.env.POSTGRES_SSL?.toLowerCase() === 'true', + entities: ['dist/src/**/*.entity{.ts,.js}'], });