Skip to content

Commit

Permalink
feat(vc-api): external subdomainRecordsFetcher error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
georgesMouawad committed Dec 11, 2024
1 parent 98e6096 commit c337b45
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 60 deletions.
2 changes: 2 additions & 0 deletions apps/vc-api/src/api/filters/vc.api.filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -16,4 +17,5 @@ export const VCManagementApiFilters = [
CredentialsExceptionFilter,
SocialResolverNotFoundExceptionFilter,
Web3ProviderExceptionFilter,
RecordsFetchingExceptionFilter,
];
Original file line number Diff line number Diff line change
@@ -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,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Subname>;
fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts?: string[]): Promise<Subname[]>
fetchRecords(providerUrl: string, subname: string, chainId: ChainId, texts: string[]): Promise<Subname>;
fetchRecordsFromManySubnames(providerUrl: string, subnames: string[], chainId: ChainId, texts: string[]): Promise<Subname[]>
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Subname> {
let records: GetRecordsResponse;

if(texts) {
async fetchRecords(
providerUrl: string,
subname: string,
chainId: ChainId,
texts?: string[]
): Promise<Subname> {
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<Subname[]> {
let records: GetRecordsResponse[];

if(texts) {
async fetchRecordsFromManySubnames(
providerUrl: string,
subnames: string[],
chainId: ChainId,
texts: string[]
): Promise<Subname[]> {
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])
);
}
}

0 comments on commit c337b45

Please sign in to comment.