Skip to content

Commit

Permalink
Merge branch 'main' into separate-app-config-for-local-and-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
vasyl-ivanchuk authored Oct 12, 2023
2 parents 2b5d871 + 494fc98 commit aa4a47a
Show file tree
Hide file tree
Showing 22 changed files with 292 additions and 23 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/address/address.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { TransferDto } from "../transfer/transfer.dto";

const entityName = "address";

@ApiTags(entityName)
@ApiTags("Address BFF")
@Controller(entityName)
export class AddressController {
constructor(
Expand Down
39 changes: 39 additions & 0 deletions packages/api/src/api/account/account.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended";
import { BadRequestException, Logger } from "@nestjs/common";
import { L2_ETH_TOKEN_ADDRESS } from "../../common/constants";
import { BlockService } from "../../block/block.service";
import { BlockDetail } from "../../block/blockDetail.entity";
import { TransactionService } from "../../transaction/transaction.service";
import { BalanceService } from "../../balance/balance.service";
import { TransactionStatus } from "../../transaction/entities/transaction.entity";
Expand Down Expand Up @@ -104,6 +105,7 @@ describe("AccountController", () => {
beforeEach(async () => {
blockServiceMock = mock<BlockService>({
getLastBlockNumber: jest.fn().mockResolvedValue(100),
findMany: jest.fn().mockResolvedValue([]),
});
transactionServiceMock = mock<TransactionService>({
findByAddress: jest.fn().mockResolvedValue([]),
Expand Down Expand Up @@ -603,4 +605,41 @@ describe("AccountController", () => {
expect(parseAddressListPipeExceptionFactory()).toEqual(new BadRequestException("Error! Missing address"));
});
});

describe("getAccountMinedBlocks", () => {
it("returns not ok response when no blocks by miner found", async () => {
const response = await controller.getAccountMinedBlocks(address, {
page: 1,
offset: 10,
maxLimit: 100,
});
expect(response).toEqual({
status: ResponseStatus.NOTOK,
message: ResponseMessage.NO_TRANSACTIONS_FOUND,
result: [],
});
});

it("returns blocks list response when block by miner are found", async () => {
jest
.spyOn(blockServiceMock, "findMany")
.mockResolvedValue([{ number: 1, timestamp: new Date("2023-03-03") } as BlockDetail]);
const response = await controller.getAccountMinedBlocks(address, {
page: 1,
offset: 10,
maxLimit: 100,
});
expect(response).toEqual({
status: ResponseStatus.OK,
message: ResponseMessage.OK,
result: [
{
blockNumber: "1",
timeStamp: "1677801600",
blockReward: "0",
},
],
});
});
});
});
30 changes: 30 additions & 0 deletions packages/api/src/api/account/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Controller, Get, Query, Logger, UseFilters, ParseArrayPipe, BadRequestE
import { ApiTags, ApiExcludeController } from "@nestjs/swagger";
import { L2_ETH_TOKEN_ADDRESS } from "../../common/constants";
import { TokenType } from "../../token/token.entity";
import { dateToTimestamp } from "../../common/utils";
import { BlockService } from "../../block/block.service";
import { TransactionService } from "../../transaction/transaction.service";
import { TransferService } from "../../transfer/transfer.service";
Expand All @@ -25,6 +26,7 @@ import {
AccountEtherBalanceResponseDto,
AccountsEtherBalancesResponseDto,
} from "../dtos/account/accountEtherBalanceResponse.dto";
import { AccountMinedBlocksResponseDto } from "../dtos/account/accountMinedBlocksResponse.dto";
import { ApiExceptionFilter } from "../exceptionFilter";

const entityName = "account";
Expand Down Expand Up @@ -225,4 +227,32 @@ export class AccountController {
result: balance,
};
}

@Get("/getminedblocks")
public async getAccountMinedBlocks(
@Query("address", new ParseAddressPipe()) address: string,
@Query() pagingOptions: PagingOptionsWithMaxItemsLimitDto
): Promise<AccountMinedBlocksResponseDto> {
const blocks = await this.blockService.findMany({
miner: address,
...pagingOptions,
selectFields: ["number", "timestamp"],
});
if (!blocks.length) {
return {
status: ResponseStatus.NOTOK,
message: ResponseMessage.NO_TRANSACTIONS_FOUND,
result: [],
};
}
return {
status: ResponseStatus.OK,
message: ResponseMessage.OK,
result: blocks.map((block) => ({
blockNumber: block.number.toString(),
timeStamp: dateToTimestamp(block.timestamp).toString(),
blockReward: "0",
})),
};
}
}
7 changes: 7 additions & 0 deletions packages/api/src/api/api.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ describe("ApiController", () => {
});
});

describe("getAccountMinedBlocks", () => {
it("returns null as it is defined only to appear in docs and cannot be called", async () => {
const result = await controller.getAccountMinedBlocks({ page: 1, offset: 10, maxLimit: 1000 });
expect(result).toBe(null);
});
});

describe("getBlockNumberByTimestamp", () => {
it("returns null as it is defined only to appear in docs and cannot be called", async () => {
const result = await controller.getBlockNumberByTimestamp();
Expand Down
23 changes: 23 additions & 0 deletions packages/api/src/api/api.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
AccountsEtherBalancesResponseDto,
} from "./dtos/account/accountEtherBalanceResponse.dto";
import { AccountTokenBalanceResponseDto } from "./dtos/account/accountTokenBalanceResponse.dto";
import { AccountMinedBlock } from "./dtos/account/accountMinedBlock.dto";
import { AccountMinedBlocksResponseDto } from "./dtos/account/accountMinedBlocksResponse.dto";
import { BlockNumberResponseDto } from "./dtos/block/blockNumberResponse.dto";
import { BlockCountdownResponseDto } from "./dtos/block/blockCountdownResponse.dto";
import { BlockRewardResponseDto } from "./dtos/block/blockRewardResponse.dto";
Expand Down Expand Up @@ -364,6 +366,27 @@ export class ApiController {
return null;
}

@ApiTags("Account API")
@Get("api?module=account&action=getminedblocks")
@ApiOperation({ summary: "Get list of Blocks Validated by Address" })
@ApiQuery({
name: "address",
description: "The address to get validated blocks by",
example: "0x0000000000000000000000000000000000000000",
required: true,
})
@ApiExtraModels(AccountMinedBlock)
@ApiOkResponse({
description: "Blocks validated by address",
type: AccountMinedBlocksResponseDto,
})
public async getAccountMinedBlocks(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query() pagingOptions: PagingOptionsWithMaxItemsLimitDto
): Promise<AccountMinedBlocksResponseDto> {
return null;
}

@ApiTags("Block API")
@Get("api?module=block&action=getblocknobytime")
@ApiOperation({ summary: "Retrieve block number closest to a specific timestamp" })
Expand Down
24 changes: 24 additions & 0 deletions packages/api/src/api/dtos/account/accountMinedBlock.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiProperty } from "@nestjs/swagger";

export class AccountMinedBlock {
@ApiProperty({
type: String,
description: "The number (height) of the block",
example: "3233097",
})
public readonly blockNumber: string;

@ApiProperty({
type: String,
description: "The timestamp of the block",
example: "1679988122",
})
public readonly timeStamp: string;

@ApiProperty({
type: String,
description: "Reward for the block",
example: "1000",
})
public readonly blockReward: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiProperty } from "@nestjs/swagger";
import { ResponseBaseDto } from "../common/responseBase.dto";
import { AccountMinedBlock } from "./accountMinedBlock.dto";

export class AccountMinedBlocksResponseDto extends ResponseBaseDto {
@ApiProperty({
description: "List of blocks validated by address",
type: AccountMinedBlock,
isArray: true,
})
public readonly result: AccountMinedBlock[];
}
8 changes: 4 additions & 4 deletions packages/api/src/api/log/log.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("LogController", () => {
const address = "address";
beforeEach(async () => {
logServiceMock = mock<LogService>({
findLogs: jest.fn().mockResolvedValue([
findMany: jest.fn().mockResolvedValue([
{
logIndex: 1,
},
Expand Down Expand Up @@ -52,8 +52,8 @@ describe("LogController", () => {
0,
10
);
expect(logServiceMock.findLogs).toBeCalledTimes(1);
expect(logServiceMock.findLogs).toBeCalledWith({
expect(logServiceMock.findMany).toBeCalledTimes(1);
expect(logServiceMock.findMany).toBeCalledWith({
address,
fromBlock: 0,
toBlock: 10,
Expand Down Expand Up @@ -89,7 +89,7 @@ describe("LogController", () => {
});

it("returns not ok response and empty logs list when logs are not found", async () => {
(logServiceMock.findLogs as jest.Mock).mockResolvedValueOnce([]);
(logServiceMock.findMany as jest.Mock).mockResolvedValueOnce([]);
const response = await controller.getLogs(
address,
{
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/api/log/log.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class LogController {
@Query("fromBlock", new ParseLimitedIntPipe({ min: 0, isOptional: true })) fromBlock?: number,
@Query("toBlock", new ParseLimitedIntPipe({ min: 0, isOptional: true })) toBlock?: number
): Promise<LogsResponseDto> {
const logs = await this.logService.findLogs({
const logs = await this.logService.findMany({
address,
fromBlock,
toBlock,
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum ApiAccountAction {
TokenBalance = "tokenbalance",
TokenTransfers = "tokentx",
NFTTransfers = "tokennfttx",
GetMinedBlocks = "getminedblocks",
}

export enum ApiContractAction {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/batch/batch.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BatchDetailsDto } from "./batchDetails.dto";

const entityName = "batches";

@ApiTags(entityName)
@ApiTags("Batch BFF")
@Controller(entityName)
export class BatchController {
constructor(private readonly batchService: BatchService) {}
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/block/block.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BlockDetailDto } from "./blockDetail.dto";

const entityName = "blocks";

@ApiTags(entityName)
@ApiTags("Block BFF")
@Controller(entityName)
export class BlockController {
constructor(private readonly blockService: BlockService) {}
Expand Down
79 changes: 78 additions & 1 deletion packages/api/src/block/block.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getRepositoryToken } from "@nestjs/typeorm";
import { Repository, SelectQueryBuilder, FindOptionsOrder } from "typeorm";
import { Pagination, IPaginationMeta } from "nestjs-typeorm-paginate";
import * as utils from "../common/utils";
import { BlockService } from "./block.service";
import { BlockService, FindManyOptions } from "./block.service";
import { Block } from "./block.entity";
import { BlockDetail } from "./blockDetail.entity";

Expand Down Expand Up @@ -299,4 +299,81 @@ describe("BlockService", () => {
expect(number).toBe(1000);
});
});

describe("findMany", () => {
let queryBuilderMock;
let filterOptions: FindManyOptions;

beforeEach(() => {
queryBuilderMock = mock<SelectQueryBuilder<BlockDetail>>({
getMany: jest.fn().mockResolvedValue([
{
number: 1,
timestamp: new Date("2023-03-03"),
},
]),
});
(blockDetailRepositoryMock.createQueryBuilder as jest.Mock).mockReturnValue(queryBuilderMock);

filterOptions = {
miner: "address",
};
});

it("creates query builder with proper params", async () => {
await service.findMany(filterOptions);
expect(blockDetailRepositoryMock.createQueryBuilder).toHaveBeenCalledTimes(1);
expect(blockDetailRepositoryMock.createQueryBuilder).toHaveBeenCalledWith("block");
});

it("selects specified fields", async () => {
await service.findMany({
...filterOptions,
selectFields: ["number", "timestamp"],
});
expect(queryBuilderMock.addSelect).toHaveBeenCalledTimes(1);
expect(queryBuilderMock.addSelect).toHaveBeenCalledWith(["number", "timestamp"]);
});

it("adds where condition for miner when specified", async () => {
await service.findMany(filterOptions);
expect(queryBuilderMock.where).toHaveBeenCalledTimes(1);
expect(queryBuilderMock.where).toHaveBeenCalledWith({
miner: "address",
});
});

it("does not add where condition for miner when not specified", async () => {
await service.findMany({});
expect(queryBuilderMock.where).not.toBeCalled();
});

it("sets offset and limit", async () => {
await service.findMany({
page: 10,
offset: 100,
});
expect(queryBuilderMock.offset).toHaveBeenCalledTimes(1);
expect(queryBuilderMock.offset).toHaveBeenCalledWith(900);
expect(queryBuilderMock.limit).toHaveBeenCalledTimes(1);
expect(queryBuilderMock.limit).toHaveBeenCalledWith(100);
});

it("orders by block number DESC", async () => {
await service.findMany({});
expect(queryBuilderMock.orderBy).toHaveBeenCalledTimes(1);
expect(queryBuilderMock.orderBy).toHaveBeenCalledWith("block.number", "DESC");
});

it("returns block list", async () => {
const result = await service.findMany({});
expect(queryBuilderMock.getMany).toHaveBeenCalledTimes(1);
expect(result).toEqual([
{
number: 1,
timestamp: new Date("2023-03-03"),
},
]);
});
});
});
21 changes: 21 additions & 0 deletions packages/api/src/block/block.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { IPaginationOptions } from "../common/types";
import { Block } from "./block.entity";
import { BlockDetail } from "./blockDetail.entity";

export interface FindManyOptions {
miner?: string;
page?: number;
offset?: number;
selectFields?: (keyof BlockDetail)[];
}

@Injectable()
export class BlockService {
public constructor(
Expand Down Expand Up @@ -82,4 +89,18 @@ export class BlockService {

return await paginate<Block>(queryBuilder, paginationOptions, () => this.count(filterOptions));
}

public async findMany({ miner, page = 1, offset = 10, selectFields }: FindManyOptions): Promise<BlockDetail[]> {
const queryBuilder = this.blockDetailsRepository.createQueryBuilder("block");
queryBuilder.addSelect(selectFields);
if (miner) {
queryBuilder.where({
miner,
});
}
queryBuilder.offset((page - 1) * offset);
queryBuilder.limit(offset);
queryBuilder.orderBy("block.number", "DESC");
return await queryBuilder.getMany();
}
}
Loading

0 comments on commit aa4a47a

Please sign in to comment.