diff --git a/apps/vc-api/src/api/filters/vc.api.filters.ts b/apps/vc-api/src/api/filters/vc.api.filters.ts index cfb504c..b982d9e 100644 --- a/apps/vc-api/src/api/filters/vc.api.filters.ts +++ b/apps/vc-api/src/api/filters/vc.api.filters.ts @@ -6,6 +6,7 @@ import { CredentialsExceptionFilter } from './credentials/credentials.filter'; import { AuthenticationExceptionFilter } from './auth/authentication.filter'; import { JustaNameInitializerExceptionFilter } from './auth/justaName-intializer.filter'; import { Web3ProviderExceptionFilter } from './web3-provider/web3-provider.filter'; +import { RecordsFetchingExceptionFilter } from './verify-records/records-fetching.filter'; export const VCManagementApiFilters = [ OTPExceptionFilter, @@ -16,4 +17,5 @@ export const VCManagementApiFilters = [ CredentialsExceptionFilter, SocialResolverNotFoundExceptionFilter, Web3ProviderExceptionFilter, + RecordsFetchingExceptionFilter, ]; diff --git a/apps/vc-api/src/api/filters/verify-records/records-fetching.filter.ts b/apps/vc-api/src/api/filters/verify-records/records-fetching.filter.ts new file mode 100644 index 0000000..aa36540 --- /dev/null +++ b/apps/vc-api/src/api/filters/verify-records/records-fetching.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { RecordsFetchingException } from '../../../core/domain/exceptions/RecordsFetching.exception'; + +@Catch(RecordsFetchingException) +export class RecordsFetchingExceptionFilter extends BaseExceptionFilter { + catch(exception: RecordsFetchingException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.BAD_REQUEST; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts b/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts index 0155b6e..8f4e239 100644 --- a/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts +++ b/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts @@ -4,6 +4,6 @@ import { Subname } from '../../domain/entities/subname'; export const SUBNAME_RECORDS_FETCHER = 'SUBNAME_RECORDS_FETCHER'; export interface ISubnameRecordsFetcher { - fetchRecords(providerUrl: string, subname: string, chainId: ChainId, texts?: string[]): Promise; - fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts?: string[]): Promise + fetchRecords(providerUrl: string, subname: string, chainId: ChainId, texts: string[]): Promise; + fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts: string[]): Promise } diff --git a/apps/vc-api/src/core/domain/exceptions/RecordsFetching.exception.ts b/apps/vc-api/src/core/domain/exceptions/RecordsFetching.exception.ts new file mode 100644 index 0000000..270d6ae --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/RecordsFetching.exception.ts @@ -0,0 +1,12 @@ +export class RecordsFetchingException extends Error { + constructor(message: string) { + super(message); + } + + static forSubnamesWithProvider(subnames: string[], providerUrl: string) { + const errorMessage = `Failed to fetch records for: ${subnames.join( + ', ' + )}, with provider: ${providerUrl}`; + return new RecordsFetchingException(errorMessage); + } +} diff --git a/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts b/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts index ce3d01e..ebf611e 100644 --- a/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts +++ b/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts @@ -1,113 +1,115 @@ import { ISubnameRecordsFetcher } from '../../core/applications/verify-records/isubname-records.fetcher'; -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Subname } from '../../core/domain/entities/subname'; -import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; -import { ENS_MANAGER_SERVICE, IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; import { ChainId } from '../../core/domain/entities/environment'; import { GetRecordsResponse } from '../../core/applications/ens-manager/responses/get-records.response'; import { getRecords, batch } from '@ensdomains/ensjs/public'; -import {createClient, http} from "viem"; -import {mainnet, sepolia} from "viem/chains"; +import { createClient, http } from 'viem'; +import { mainnet, sepolia } from 'viem/chains'; +import { RecordsFetchingException } from '../../core/domain/exceptions/RecordsFetching.exception'; @Injectable() export class SubnameRecordsFetcher implements ISubnameRecordsFetcher { - constructor( - @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, - @Inject(ENS_MANAGER_SERVICE) private readonly ensManagerService: IEnsManagerService - ) {} + constructor() {} - async fetchRecords(providerUrl: string, subname: string, chainId: ChainId, texts?: string[]): Promise { - let records: GetRecordsResponse; - - if(texts) { + async fetchRecords( + providerUrl: string, + subname: string, + chainId: ChainId, + texts?: string[] + ): Promise { + try { const client = createClient({ chain: chainId === 1 ? mainnet : sepolia, - transport: http(providerUrl) + transport: http(providerUrl), }); - const recordsFromEns = await getRecords(client,{ + const recordsFromEns = await getRecords(client, { name: subname, texts: texts, coins: ['eth'], - contentHash: true + contentHash: true, }); - records = { + const records: GetRecordsResponse = { ...recordsFromEns, - isJAN: false - } - } else{ - records = await this.ensManagerService.getRecords({ - ens: subname, - chainId: chainId, - }) - } + isJAN: false, + }; - return this.mapSubnameRecordsResponseToSubname(subname, records); + return this.mapSubnameRecordsResponseToSubname(subname, records); + } catch (error) { + throw RecordsFetchingException.forSubnamesWithProvider( + [subname], + providerUrl + ); + } } - async fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts?: string[]): Promise { - let records: GetRecordsResponse[]; - - if(texts) { + async fetchRecordsFromManySubnames( + providerUrl: string, + subnames: string[], + chainId: ChainId, + texts: string[] + ): Promise { + try { const client = createClient({ chain: chainId === 1 ? mainnet : sepolia, - transport: http(providerUrl) + transport: http(providerUrl), }); - const batchRequests = subnames.map(name => + const batchRequests = subnames.map((name) => getRecords.batch({ name, texts, coins: ['eth'], - contentHash: true + contentHash: true, }) ); const recordsFromEns = await batch(client, ...batchRequests); - records = subnames.map((_, index) => ({ - ...recordsFromEns[index], - isJAN: false - } - )); + const records: GetRecordsResponse[] = subnames.map((_, index) => ({ + ...recordsFromEns[index], + isJAN: false, + })); return this.mapSubnameRecordsResponsesToSubnameArray(subnames, records); - - // TODO: the following code is unnecessary, we should remove it - } else { - const promises = subnames.map(subname => - this.ensManagerService.getRecords({ - ens: subname, - chainId: chainId, - }) + } catch (error) { + throw RecordsFetchingException.forSubnamesWithProvider( + subnames, + providerUrl ); - - const records = await Promise.all(promises); - return this.mapSubnameRecordsResponsesToSubnameArray(subnames, records); } } - mapSubnameRecordsResponseToSubname(subname: string, records: GetRecordsResponse): Subname { + mapSubnameRecordsResponseToSubname( + subname: string, + records: GetRecordsResponse + ): Subname { return { subname: subname, metadata: { contentHash: records.contentHash?.decoded ?? '', - addresses: records.coins.map(coin => ({ + addresses: records.coins.map((coin) => ({ id: String(coin.id), coinType: coin.id, address: coin.value, - metadataId: '' + metadataId: '', })), - textRecords: records.texts.map(text => ({ + textRecords: records.texts.map((text) => ({ key: text.key, - value: text.value - })) - } + value: text.value, + })), + }, }; } - mapSubnameRecordsResponsesToSubnameArray(subnames: string[], records: GetRecordsResponse[]): Subname[] { - return subnames.map((subname, index) => this.mapSubnameRecordsResponseToSubname(subname, records[index])); + mapSubnameRecordsResponsesToSubnameArray( + subnames: string[], + records: GetRecordsResponse[] + ): Subname[] { + return subnames.map((subname, index) => + this.mapSubnameRecordsResponseToSubname(subname, records[index]) + ); } }