Skip to content

Commit

Permalink
feat: add review contracts endpoint. (#1128)
Browse files Browse the repository at this point in the history
* feat: new endpoint.
  • Loading branch information
b4rtaz authored May 17, 2023
1 parent 00de1c1 commit 98035a8
Show file tree
Hide file tree
Showing 25 changed files with 611 additions and 24 deletions.
7 changes: 7 additions & 0 deletions .changeset/tall-adults-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@moralisweb3/common-evm-utils': patch
'@moralisweb3/evm-api': patch
'moralis': patch
---

Added the new endpoint method to the EVM API module: `Moralis.EvmApi.utils.reviewContracts`.
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,18 @@ export class OperationFileGenerator {
output.write(0, '}');
output.newLine();

if (responseTypeCodes && this.configuration.supportV2) {
if (this.configuration.supportV2) {
// TODO: this part is added to support the V2 approach. It should be removed after the V2 is removed.
output.write(0, `export type ${className}Response = ${responseTypeCodes.valueTypeCode};`);
output.write(0, `export type ${className}ResponseJSON = ${responseTypeCodes.jsonTypeCode};`);
output.newLine();

if (responseTypeCodes) {
output.write(0, `export type ${className}Response = ${responseTypeCodes.valueTypeCode};`);
output.write(0, `export type ${className}ResponseJSON = ${responseTypeCodes.jsonTypeCode};`);
output.newLine();
}
if (bodyTypeCodes) {
output.write(0, `export type ${className}Body = ${bodyTypeCodes.inputOrValueTypeCode};`);
output.newLine();
}
}

output.write(0, `export const ${className} = {`);
Expand Down
120 changes: 120 additions & 0 deletions packages/apiGenerator/src/reader/OpenApiReader.inlineEnum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { OpenApiReader } from './OpenApiReader';
import { OpenApiReaderConfiguration } from './OpenApiReaderConfiguration';

describe('OpenApiReader', () => {
it('inline enum', () => {
const configuration: OpenApiReaderConfiguration = {
v3: {
operations: {
groupRef: '#/operationId',
isEnabledRef: '#/operationId',
},
},
};
const result = OpenApiReader.create(
{
openapi: '3.0.0',
info: {
title: 'test',
version: '1',
},
paths: {
'/contracts-review': {
post: {
operationId: 'reviewContracts',
requestBody: {
description: 'Body',
required: true,
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/ContractsReviewDto',
},
},
},
},
parameters: [],
responses: {
'200': {
description: 'Response',
content: {
'application/json': {
schema: {
type: 'object',
},
},
},
},
},
},
},
},
components: {
schemas: {
contractsReviewItem: {
required: ['report_type'],
properties: {
report_type: {
type: 'string',
enum: ['spam', 'not_spam'],
},
},
},
ContractsReviewDto: {
required: ['contracts'],
properties: {
contracts: {
type: 'array',
items: {
$ref: '#/components/schemas/contractsReviewItem',
},
},
},
},
},
},
},
configuration,
).read();

const operation = result.operations[0];
expect(operation.operationId).toBe('reviewContracts');
expect(operation.httpMethod).toBe('post');
expect(operation.routePattern).toBe('/contracts-review');
expect(operation.body).toBeDefined();

const body = operation.body!;
expect(body.descriptor.ref.toString()).toBe('#/components/schemas/ContractsReviewDto');

expect(result.complexTypes).toHaveLength(2);

const complexType0 = result.complexTypes[0];
expect(complexType0.descriptor.ref.toString()).toBe('#/components/schemas/ContractsReviewDto');
expect(complexType0.properties).toHaveLength(1);

const complexType1 = result.complexTypes[1];
expect(complexType1.descriptor.ref.toString()).toBe('#/components/schemas/contractsReviewItem');
expect(complexType1.properties).toHaveLength(1);

expect(result.simpleTypes).toHaveLength(2);

const simpleType0 = result.simpleTypes[0];
expect(simpleType0.descriptor.ref.toString()).toBe(
'#/paths/~1contracts-review/post/responses/200/content/application~1json/schema',
);
expect(simpleType0.descriptor.isArray).toBe(false);
expect(simpleType0.descriptor.typeName.toString()).toBe('reviewContracts');
expect(simpleType0.nativeType).toBe('object');
expect(simpleType0.enum).toBeUndefined();

const simpleType1 = result.simpleTypes[1];
expect(simpleType1.descriptor.ref.toString()).toBe(
'#/components/schemas/contractsReviewItem/properties/report_type',
);
expect(simpleType1.descriptor.isArray).toBe(false);
expect(simpleType1.descriptor.typeName.toString()).toBe('contractsReviewItem_report_type_Enum');
expect(simpleType1.nativeType).toContain('string');
expect(simpleType1.enum).toContain('spam');
expect(simpleType1.enum).toContain('not_spam');
});
});
6 changes: 6 additions & 0 deletions packages/apiGenerator/src/reader/v3/TypeDescriptorV3Reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UnionV3Reader } from './UnionV3Reader';
import { NativeTypeNormalizer } from '../utils/NativeTypeNormalizer';

const ITEM_TYPE_NAME_SUFFIX = 'Item';
const ENUM_TYPE_NAME_SUFFIX = 'Enum';
const COMPONENT_SCHEMA_$REF_PREFIX = '#/components/schemas/';

export class TypeDescriptorV3Reader {
Expand Down Expand Up @@ -64,6 +65,11 @@ export class TypeDescriptorV3Reader {
return new NativeTypeDescriptor(true, parentRef, nativeType);
}

if (schema.enum) {
const enumTypeName = defaultTypeName.add(ENUM_TYPE_NAME_SUFFIX);
return new ReferenceTypeDescriptor(false, parentRef, enumTypeName);
}

if (schema.type === 'object') {
return new ReferenceTypeDescriptor(false, parentRef, defaultTypeName);
}
Expand Down
11 changes: 9 additions & 2 deletions packages/apiGenerator/src/reader/v3/TypesV3Reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,15 @@ export class TypesV3Reader {
throw new Error(`anyOf, allOf and anyOf is not supported (${pointer.ref})`);
}

let nativeType = scheme.type && NativeTypeNormalizer.normalize(scheme.type);
if (!nativeType) {
let nativeType: string | null = null;
if (scheme.enum) {
if (scheme.enum.some((item) => typeof item !== 'string')) {
throw new Error(`Only string enum is supported (${pointer.ref.toString()})`);
}
nativeType = 'string';
} else if (scheme.type) {
nativeType = NativeTypeNormalizer.normalize(scheme.type);
} else {
nativeType = 'object';
console.warn(`[no-schema-type] Not defined schema type for complex type (${pointer.ref.toString()})`);
}
Expand Down
11 changes: 8 additions & 3 deletions packages/apiUtils/scripts/generate-client.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const sourcePackage = require(sourcePackageName);
const fs = require('fs');
const { determineOperationType } = require('@moralisweb3/common-core');

const operationsV2 = sourcePackage.operationsV2;
const { operationsV2 } = sourcePackage;
const operationsV3 = sourcePackage.operations || [];

const operations = [
Expand All @@ -27,6 +27,8 @@ const operations = [
operationVarName: `${operation.name}Operation`,
requestClassName: `${capitalizeFirst(operation.name)}Request`,
responseClassName: `${capitalizeFirst(operation.name)}ResponseAdapter`,
responseJSONClassName: null,
bodyClassName: null,
hasRequest: Boolean(operation.urlPathParamNames || operation.urlSearchParamNames || operation.bodyParamNames),
hasBody: false,
type: determineOperationType(operation),
Expand All @@ -42,6 +44,7 @@ const operations = [
requestClassName: `${capitalizedName}OperationRequest`,
responseClassName: `${capitalizedName}OperationResponse`,
responseJSONClassName: `${capitalizedName}OperationResponseJSON`,
bodyClassName: operation.hasBody ? `${capitalizedName}OperationBody` : null,
hasRequest: operation.parameterNames.length > 0,
hasBody: operation.hasBody,
type: isPaginated ? 'paginatedV3' : 'V3',
Expand Down Expand Up @@ -105,8 +108,7 @@ for (const groupName of uniqueGroupNames) {
methodArgs = operation.hasRequest ? `request: ${operation.requestClassName}` : '';
fetchArgs = operation.hasRequest ? 'request, ' : '{}, ';
if (operation.hasBody) {
// TODO: add support for body.
methodArgs += ', body: unknown';
methodArgs += `, body: ${operation.bodyClassName}`;
fetchArgs += 'body';
} else {
fetchArgs += 'null';
Expand All @@ -118,6 +120,9 @@ for (const groupName of uniqueGroupNames) {
if (operation.hasRequest) {
sourcePackageImports.add(operation.requestClassName);
}
if (operation.hasBody) {
sourcePackageImports.add(operation.bodyClassName);
}
sourcePackageImports.add(operation.responseClassName);
apiUtilsPackageImports.add(resolverClassName);

Expand Down
3 changes: 2 additions & 1 deletion packages/common/evmUtils/generator.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"getTopERC20TokensByMarketCap",
"getTopERC20TokensByPriceMovers",
"getTopNFTCollectionsByMarketCap",
"getHottestNFTCollectionsByTradingVolume"
"getHottestNFTCollectionsByTradingVolume",
"reviewContracts"
]
}
}
Expand Down
21 changes: 20 additions & 1 deletion packages/common/evmUtils/src/generated/client/abstractClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { GetTopNFTCollectionsByMarketCapOperation, GetTopNFTCollectionsByMarketC
import { EvmMarketDataTopNFTCollectionByMarketCapItem, EvmMarketDataTopNFTCollectionByMarketCapItemJSON } from '../types/EvmMarketDataTopNFTCollectionByMarketCapItem';
import { GetHottestNFTCollectionsByTradingVolumeOperation, GetHottestNFTCollectionsByTradingVolumeOperationRequest, GetHottestNFTCollectionsByTradingVolumeOperationRequestJSON } from '../operations/GetHottestNFTCollectionsByTradingVolumeOperation';
import { EvmMarketDataHottestNFTCollectionByTradingVolumeItem, EvmMarketDataHottestNFTCollectionByTradingVolumeItemJSON } from '../types/EvmMarketDataHottestNFTCollectionByTradingVolumeItem';
import { ReviewContractsOperation, ReviewContractsOperationRequest, ReviewContractsOperationRequestJSON } from '../operations/ReviewContractsOperation';
import { EvmReviewContracts, EvmReviewContractsJSON } from '../types/EvmReviewContracts';
import { EvmContractsReviewDto, EvmContractsReviewDtoInput, EvmContractsReviewDtoJSON } from '../types/EvmContractsReviewDto';

export interface OperationV3<Request, RequestJSON, Response, ResponseJSON, Body, BodyJSON> {
operationId: string;
Expand Down Expand Up @@ -98,7 +101,7 @@ export abstract class AbstractClient {
* @param {String} [request.toDate] The end date from which to get the transfers (any format that is accepted by momentjs)
* * Provide the param 'to_block' or 'to_date'
* * If 'to_date' and 'to_block' are provided, 'to_block' will be used. (optional)
* @param {String} [request.marketplace] Marketplace from which to get the trades (only OpenSea is supported at the moment) (optional)
* @param {Object} [request.marketplace] Marketplace from which to get the trades (only OpenSea is supported at the moment) (optional)
* @param {String} [request.cursor] The cursor returned in the previous response (used for getting the next page). (optional)
* @param {Number} [request.limit] The desired page size of the result. (optional)
* @param {Boolean} [request.disableTotal] If the result should skip returning the total count (Improves performance). (optional)
Expand Down Expand Up @@ -156,5 +159,21 @@ export abstract class AbstractClient {
EvmEndpointWeights[],
EvmEndpointWeightsJSON[]
>(EndpointWeightsOperation),
/**
* @description Review contracts as spam or not spam
* @param request Request with parameters.
* @param {Object} [request.chain] The chain to query (optional)
* @param body Request body.
* @param {Object[]} body.contracts The contracts to be reported
* @returns {Object} Response for the request.
*/
reviewContracts: this.createEndpointWithBody<
ReviewContractsOperationRequest,
ReviewContractsOperationRequestJSON,
EvmReviewContracts,
EvmReviewContractsJSON,
EvmContractsReviewDtoInput | EvmContractsReviewDto,
EvmContractsReviewDtoJSON
>(ReviewContractsOperation),
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EvmChain, EvmChainInput, EvmChainJSON, EvmAddress, EvmAddressInput, EvmAddressJSON } from '../../dataTypes';
import { EvmGetNFTTradesMarketplaceEnum, EvmGetNFTTradesMarketplaceEnumValue, EvmGetNFTTradesMarketplaceEnumInput, EvmGetNFTTradesMarketplaceEnumJSON } from '../types/EvmGetNFTTradesMarketplaceEnum';
import { EvmTradeCollection, EvmTradeCollectionJSON } from '../types/EvmTradeCollection';

// request parameters:
Expand Down Expand Up @@ -43,7 +44,7 @@ export interface GetNFTTradesOperationRequest {
/**
* @description Marketplace from which to get the trades (only OpenSea is supported at the moment)
*/
readonly marketplace?: string;
readonly marketplace?: EvmGetNFTTradesMarketplaceEnumInput | EvmGetNFTTradesMarketplaceEnumValue;
/**
* @description The cursor returned in the previous response (used for getting the next page).
*/
Expand All @@ -68,7 +69,7 @@ export interface GetNFTTradesOperationRequestJSON {
readonly to_block?: string;
readonly from_date?: string;
readonly to_date?: string;
readonly marketplace?: string;
readonly marketplace?: EvmGetNFTTradesMarketplaceEnumJSON;
readonly cursor?: string;
readonly limit?: number;
readonly disable_total?: boolean;
Expand Down Expand Up @@ -97,7 +98,7 @@ export const GetNFTTradesOperation = {
const toBlock = request.toBlock;
const fromDate = request.fromDate;
const toDate = request.toDate;
const marketplace = request.marketplace;
const marketplace = request.marketplace ? EvmGetNFTTradesMarketplaceEnum.create(request.marketplace) : undefined;
const cursor = request.cursor;
const limit = request.limit;
const disableTotal = request.disableTotal;
Expand All @@ -108,7 +109,7 @@ export const GetNFTTradesOperation = {
to_block: toBlock,
from_date: fromDate,
to_date: toDate,
marketplace: marketplace,
marketplace: marketplace ? marketplace : undefined,
cursor: cursor,
limit: limit,
disable_total: disableTotal,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { EvmChain, EvmChainInput, EvmChainJSON } from '../../dataTypes';
import { EvmReviewContracts, EvmReviewContractsJSON } from '../types/EvmReviewContracts';
import { EvmContractsReviewDto, EvmContractsReviewDtoInput, EvmContractsReviewDtoJSON } from '../types/EvmContractsReviewDto';

// request parameters:
// - chain ($ref: #/components/schemas/chainList)

export interface ReviewContractsOperationRequest {
/**
* @description The chain to query
*/
readonly chain?: EvmChainInput | EvmChain;
}

export interface ReviewContractsOperationRequestJSON {
readonly chain?: EvmChainJSON;
}

export type ReviewContractsOperationResponse = EvmReviewContracts;
export type ReviewContractsOperationResponseJSON = EvmReviewContractsJSON;

export type ReviewContractsOperationBody = EvmContractsReviewDtoInput | EvmContractsReviewDto;

export const ReviewContractsOperation = {
operationId: "reviewContracts",
groupName: "utils",
httpMethod: "post",
routePattern: "/contracts-review",
parameterNames: ["chain"],
hasResponse: true,
hasBody: true,

parseResponse(json: EvmReviewContractsJSON): EvmReviewContracts {
return EvmReviewContracts.fromJSON(json);
},

serializeRequest(request: ReviewContractsOperationRequest): ReviewContractsOperationRequestJSON {
const chain = request.chain ? EvmChain.create(request.chain) : undefined;
return {
chain: chain ? chain.toJSON() : undefined,
};
},

serializeBody(body: EvmContractsReviewDtoInput | EvmContractsReviewDto): EvmContractsReviewDtoJSON {
const value = EvmContractsReviewDto.create(body);
return value.toJSON();
},
}
1 change: 1 addition & 0 deletions packages/common/evmUtils/src/generated/operations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export * from './GetTopERC20TokensByMarketCapOperation';
export * from './GetTopERC20TokensByPriceMoversOperation';
export * from './GetTopNFTCollectionsByMarketCapOperation';
export * from './GetHottestNFTCollectionsByTradingVolumeOperation';
export * from './ReviewContractsOperation';
export * from './operations';
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GetTopERC20TokensByMarketCapOperation } from './GetTopERC20TokensByMark
import { GetTopERC20TokensByPriceMoversOperation } from './GetTopERC20TokensByPriceMoversOperation';
import { GetTopNFTCollectionsByMarketCapOperation } from './GetTopNFTCollectionsByMarketCapOperation';
import { GetHottestNFTCollectionsByTradingVolumeOperation } from './GetHottestNFTCollectionsByTradingVolumeOperation';
import { ReviewContractsOperation } from './ReviewContractsOperation';

export const operations = [
GetNFTTradesOperation,
Expand All @@ -16,4 +17,5 @@ export const operations = [
GetTopERC20TokensByPriceMoversOperation,
GetTopNFTCollectionsByMarketCapOperation,
GetHottestNFTCollectionsByTradingVolumeOperation,
ReviewContractsOperation,
];
Loading

1 comment on commit 98035a8

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage

Title Lines Statements Branches Functions
api-utils Coverage: 20%
20.6% (61/296) 20.48% (17/83) 19.04% (12/63)
auth Coverage: 89%
92.45% (98/106) 83.33% (20/24) 86.66% (26/30)
evm-api Coverage: 96%
96.87% (93/96) 66.66% (6/9) 95.23% (60/63)
common-aptos-utils Coverage: 4%
4.56% (151/3306) 4.49% (25/556) 5.53% (45/813)
common-evm-utils Coverage: 65%
65.47% (1532/2340) 26.89% (256/952) 44.63% (416/932)
sol-api Coverage: 96%
97.5% (39/40) 66.66% (6/9) 93.33% (14/15)
common-sol-utils Coverage: 74%
74.55% (167/224) 66.66% (18/27) 65.38% (51/78)
common-streams-utils Coverage: 90%
90.73% (1204/1327) 73.63% (363/493) 82.07% (444/541)
streams Coverage: 88%
88.2% (576/653) 68.81% (64/93) 88.02% (125/142)

Please sign in to comment.