diff --git a/packages/apps/human-app/server/src/modules/job-assignment/job-assignment.mapper.profile.ts b/packages/apps/human-app/server/src/modules/job-assignment/job-assignment.mapper.profile.ts index 1504643ae3..b70fb99543 100644 --- a/packages/apps/human-app/server/src/modules/job-assignment/job-assignment.mapper.profile.ts +++ b/packages/apps/human-app/server/src/modules/job-assignment/job-assignment.mapper.profile.ts @@ -99,4 +99,4 @@ export class JobAssignmentProfile extends AutomapperProfile { ); }; } -} \ No newline at end of file +} diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/model/oracle-discovery.model.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/model/oracle-discovery.model.ts index 7be07be657..5501ed9514 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/model/oracle-discovery.model.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/model/oracle-discovery.model.ts @@ -1,4 +1,6 @@ import { IOperator } from '@human-protocol/sdk'; +import { AutoMap } from '@automapper/classes'; +import { ApiPropertyOptional } from '@nestjs/swagger'; export class OracleDiscoveryResponse implements IOperator { address: string; @@ -12,3 +14,12 @@ export class OracleDiscoveryResponse implements IOperator { this.jobTypes = jobTypes; } } +export class OracleDiscoveryDto { + @AutoMap() + @ApiPropertyOptional() + selected_job_types?: string[]; +} +export class OracleDiscoveryCommand { + @AutoMap() + selectedJobTypes?: string[]; +} diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.controller.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.controller.ts index e980f63eeb..ed8e069ca4 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.controller.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.controller.ts @@ -1,16 +1,38 @@ -import { Controller, Get, UsePipes, ValidationPipe } from '@nestjs/common'; +import { + Controller, + Get, + Query, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { OracleDiscoveryService } from './oracle-discovery.service'; -import { OracleDiscoveryResponse } from './model/oracle-discovery.model'; +import { + OracleDiscoveryCommand, + OracleDiscoveryDto, + OracleDiscoveryResponse, +} from './model/oracle-discovery.model'; +import { InjectMapper } from '@automapper/nestjs'; +import { Mapper } from '@automapper/core'; @Controller() export class OracleDiscoveryController { - constructor(private readonly service: OracleDiscoveryService) {} + constructor( + private readonly service: OracleDiscoveryService, + @InjectMapper() private readonly mapper: Mapper, + ) {} @ApiTags('Oracle-Discovery') @Get('/oracles') @ApiOperation({ summary: 'Oracles discovery' }) @UsePipes(new ValidationPipe()) - public getOracles(): Promise { - return this.service.processOracleDiscovery(); + public getOracles( + @Query() dto?: OracleDiscoveryDto, + ): Promise { + const command = this.mapper.map( + dto, + OracleDiscoveryDto, + OracleDiscoveryCommand, + ); + return this.service.processOracleDiscovery(command); } } diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.mapper.profile.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.mapper.profile.ts new file mode 100644 index 0000000000..fd3e1bb6d0 --- /dev/null +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.mapper.profile.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; +import { + CamelCaseNamingConvention, + createMap, forMember, mapFrom, + Mapper, + namingConventions, + SnakeCaseNamingConvention, +} from '@automapper/core'; +import { + OracleDiscoveryCommand, + OracleDiscoveryDto, +} from './model/oracle-discovery.model'; + +@Injectable() +export class OracleDiscoveryProfile extends AutomapperProfile { + constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper) => { + createMap( + mapper, + OracleDiscoveryDto, + OracleDiscoveryCommand, + forMember( + (destination) => destination.selectedJobTypes, + mapFrom((source) => source.selected_job_types), + ), + ); + }; + } +} diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.module.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.module.ts index 73b346fe94..308783287b 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.module.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.module.ts @@ -1,8 +1,9 @@ import { Module } from '@nestjs/common'; import { OracleDiscoveryService } from './oracle-discovery.service'; +import { OracleDiscoveryProfile } from './oracle-discovery.mapper.profile'; @Module({ - providers: [OracleDiscoveryService], + providers: [OracleDiscoveryService, OracleDiscoveryProfile], exports: [OracleDiscoveryService], }) export class OracleDiscoveryModule {} diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.service.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.service.ts index 62d7d3faa4..4be5a7d309 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.service.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/oracle-discovery.service.ts @@ -1,5 +1,8 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; -import { OracleDiscoveryResponse } from './model/oracle-discovery.model'; +import { + OracleDiscoveryCommand, + OracleDiscoveryResponse, +} from './model/oracle-discovery.model'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { OperatorUtils } from '@human-protocol/sdk'; @@ -14,34 +17,70 @@ export class OracleDiscoveryService { private configService: EnvironmentConfigService, ) {} - async processOracleDiscovery(): Promise { + async processOracleDiscovery( + command: OracleDiscoveryCommand, + ): Promise { const address = this.configService.reputationOracleAddress; const chainIds = this.configService.chainIdsEnabled; - const allData = await Promise.all( + const filteredOracles = await Promise.all( chainIds.map(async (chainId) => { - let data: OracleDiscoveryResponse[] | undefined = - await this.cacheManager.get(chainId); - if (!data) { - try { - data = await OperatorUtils.getReputationNetworkOperators( - Number(chainId), - address, - this.EXCHANGE_ORACLE, - ); - await this.cacheManager.set( - chainId, - data, - this.configService.cacheTtlOracleDiscovery, - ); - } catch (error) { - this.logger.error(`Error processing chainId ${chainId}:`, error); - } - } - return data; + return this.findOraclesByChainId(chainId, address).then((oracles) => + this.getOraclesWithSelectedJobTypes( + oracles, + command.selectedJobTypes, + ), + ); }), ); - - return allData.flat().filter(Boolean) as OracleDiscoveryResponse[]; + return filteredOracles.flat().filter(Boolean) as OracleDiscoveryResponse[]; + } + private async findOraclesByChainId( + chainId: string, + address: string, + ): Promise { + let receivedOracles: OracleDiscoveryResponse[] | undefined = + await this.cacheManager.get(chainId); + if (!receivedOracles) { + try { + receivedOracles = await OperatorUtils.getReputationNetworkOperators( + Number(chainId), + address, + this.EXCHANGE_ORACLE, + ); + await this.cacheManager.set( + chainId, + receivedOracles, + this.configService.cacheTtlOracleDiscovery, + ); + } catch (error) { + this.logger.error(`Error processing chainId ${chainId}:`, error); + } + } + return receivedOracles; + } + private getOraclesWithSelectedJobTypes( + foundOracles: OracleDiscoveryResponse[] | undefined, + selectedJobTypes: string[] | undefined, + ) { + if ( + !selectedJobTypes || + selectedJobTypes.length === 0 || + !foundOracles || + foundOracles.length === 0 + ) { + return foundOracles; + } + return foundOracles.filter((oracle) => + oracle.jobTypes && oracle.jobTypes.length > 0 + ? this.areJobTypeSetsIntersect(oracle.jobTypes, selectedJobTypes) + : false, + ); + } + private areJobTypeSetsIntersect( + oracleJobTypes: string[], + requiredJobTypes: string[], + ) { + return oracleJobTypes.some((job) => requiredJobTypes.includes(job)); } } diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.controller.spec.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.controller.spec.ts index e2b5191f0e..ed5cbac527 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.controller.spec.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.controller.spec.ts @@ -4,11 +4,17 @@ import { classes } from '@automapper/classes'; import { OracleDiscoveryController } from '../oracle-discovery.controller'; import { OracleDiscoveryService } from '../oracle-discovery.service'; import { oracleDiscoveryServiceMock } from './oracle-discovery.service.mock'; -import { OracleDiscoveryResponse } from '../model/oracle-discovery.model'; +import { + OracleDiscoveryCommand, + OracleDiscoveryDto, + OracleDiscoveryResponse, +} from '../model/oracle-discovery.model'; import { generateOracleDiscoveryResponseBody } from './oracle-discovery.fixture'; +import { OracleDiscoveryProfile } from '../oracle-discovery.mapper.profile'; describe('OracleDiscoveryController', () => { let controller: OracleDiscoveryController; + let serviceMock: OracleDiscoveryService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -18,7 +24,7 @@ describe('OracleDiscoveryController', () => { strategyInitializer: classes(), }), ], - providers: [OracleDiscoveryService], + providers: [OracleDiscoveryService, OracleDiscoveryProfile], }) .overrideProvider(OracleDiscoveryService) .useValue(oracleDiscoveryServiceMock) @@ -27,6 +33,7 @@ describe('OracleDiscoveryController', () => { controller = module.get( OracleDiscoveryController, ); + serviceMock = module.get(OracleDiscoveryService); }); it('should be defined', () => { @@ -35,8 +42,18 @@ describe('OracleDiscoveryController', () => { describe('oracle discovery', () => { it('oracle discovery should be return OracleDiscoveryData', async () => { - const result: OracleDiscoveryResponse[] = await controller.getOracles(); + const dtoFixture = { + selected_job_types: ['job-type-1', 'job-type-2'], + } as OracleDiscoveryDto; + const commandFixture = { + selectedJobTypes: ['job-type-1', 'job-type-2'], + } as OracleDiscoveryCommand; + const result: OracleDiscoveryResponse[] = + await controller.getOracles(dtoFixture); const expectedResponse = generateOracleDiscoveryResponseBody(); + expect(serviceMock.processOracleDiscovery).toHaveBeenCalledWith( + commandFixture, + ); expect(result).toEqual(expectedResponse); }); }); diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.fixture.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.fixture.ts index 94bce62e53..8fd6a499eb 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.fixture.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.fixture.ts @@ -1,13 +1,26 @@ -import { OracleDiscoveryResponse } from '../model/oracle-discovery.model'; +import { + OracleDiscoveryCommand, + OracleDiscoveryResponse, +} from '../model/oracle-discovery.model'; export function generateOracleDiscoveryResponseBody() { const response1: OracleDiscoveryResponse = { address: '0xd06eac24a0c47c776Ce6826A93162c4AfC029047', role: 'role1', + jobTypes: ['job-type-3'], }; const response2: OracleDiscoveryResponse = { address: '0xd10c3402155c058D78e4D5fB5f50E125F06eb39d', role: 'role2', + jobTypes: ['job-type-1', 'job-type-3', 'job-type-4'], }; return [response1, response2]; } + +export const filledCommandFixture = { + selectedJobTypes: ['job-type-1', 'job-type-2'], +} as OracleDiscoveryCommand; +export const emptyCommandFixture = { + selectedJobTypes: [], +} as OracleDiscoveryCommand; +export const notSetCommandFixture = {} as OracleDiscoveryCommand; diff --git a/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.service.spec.ts b/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.service.spec.ts index d204ddbc4a..bb39fee926 100644 --- a/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.service.spec.ts +++ b/packages/apps/human-app/server/src/modules/oracle-discovery/spec/oracle-discovery.service.spec.ts @@ -3,10 +3,15 @@ import { Cache } from 'cache-manager'; import { OracleDiscoveryService } from '../oracle-discovery.service'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { OperatorUtils } from '@human-protocol/sdk'; -import { OracleDiscoveryResponse } from '../model/oracle-discovery.model'; +import { OracleDiscoveryCommand, OracleDiscoveryResponse } from '../model/oracle-discovery.model'; import { EnvironmentConfigService } from '../../../common/config/environment-config.service'; import { CommonConfigModule } from '../../../common/config/common-config.module'; import { ConfigModule } from '@nestjs/config'; +import { + emptyCommandFixture, filledCommandFixture, + generateOracleDiscoveryResponseBody, + notSetCommandFixture, +} from './oracle-discovery.fixture'; jest.mock('@human-protocol/sdk', () => ({ OperatorUtils: { @@ -72,7 +77,8 @@ describe('OracleDiscoveryService', () => { ]; jest.spyOn(cacheManager, 'get').mockResolvedValue(mockData); - const result = await oracleDiscoveryService.processOracleDiscovery(); + const result = + await oracleDiscoveryService.processOracleDiscovery(notSetCommandFixture); expect(result).toEqual(mockData); expect(OperatorUtils.getReputationNetworkOperators).not.toHaveBeenCalled(); @@ -89,16 +95,13 @@ describe('OracleDiscoveryService', () => { .spyOn(OperatorUtils, 'getReputationNetworkOperators') .mockResolvedValue(mockData); - const result = await oracleDiscoveryService.processOracleDiscovery(); + const result = + await oracleDiscoveryService.processOracleDiscovery(emptyCommandFixture); expect(result).toEqual(mockData); EXPECTED_CHAIN_IDS.forEach((chainId) => { expect(cacheManager.get).toHaveBeenCalledWith(chainId); - expect(cacheManager.set).toHaveBeenCalledWith( - chainId, - mockData, - TTL, - ); + expect(cacheManager.set).toHaveBeenCalledWith(chainId, mockData, TTL); expect(OperatorUtils.getReputationNetworkOperators).toHaveBeenCalledWith( Number(chainId), REPUTATION_ORACLE_ADDRESS, @@ -106,4 +109,18 @@ describe('OracleDiscoveryService', () => { ); }); }); + it('should filter responses if selectedJobTypes not empty', async () => { + const mockData: OracleDiscoveryResponse[] = + generateOracleDiscoveryResponseBody(); + + jest.spyOn(cacheManager, 'get').mockResolvedValue(undefined); + jest + .spyOn(OperatorUtils, 'getReputationNetworkOperators') + .mockResolvedValue(mockData); + + const result = + await oracleDiscoveryService.processOracleDiscovery(filledCommandFixture); + + expect(result).toEqual([mockData[1]]); + }); }); diff --git a/packages/apps/human-app/server/src/modules/statistics/statistics.controller.ts b/packages/apps/human-app/server/src/modules/statistics/statistics.controller.ts index c380e27391..56839dd0c3 100644 --- a/packages/apps/human-app/server/src/modules/statistics/statistics.controller.ts +++ b/packages/apps/human-app/server/src/modules/statistics/statistics.controller.ts @@ -17,7 +17,10 @@ import { UserStatisticsDto, UserStatisticsResponse, } from './model/user-statistics.model'; -import { Authorization, JwtPayload } from '../../common/config/params-decorators'; +import { + Authorization, + JwtPayload, +} from '../../common/config/params-decorators'; import { InjectMapper } from '@automapper/nestjs'; import { Mapper } from '@automapper/core'; import { JwtUserData } from '../../common/utils/jwt-token.model';