Skip to content

Commit

Permalink
feat: get fees by integrator (#5)
Browse files Browse the repository at this point in the history
* feat: use get by integrator for fees

* fix: only show next page url if there are more pages

* test: use new api path for tests

* docs: update readme with new commands
  • Loading branch information
kieranroneill authored Mar 18, 2024
1 parent 57e2ea9 commit 8382d15
Show file tree
Hide file tree
Showing 25 changed files with 169 additions and 169 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,14 @@ This documentation outlines the available endpoints available.

### 3.2. Useful commands

| Command | Description |
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `yarn setup` | Creates an `.env` file to the `.config/` directory. |
| `yarn start` | Runs setup and starts Docker Compose. Intended for development purposes only. |
| Command | Description |
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `yarn setup` | Creates an `.env` file to the `.config/` directory. |
| `yarn start` | Runs setup and starts Docker Compose. Intended for development purposes only. |
| `yarn test` | Runs the test script that starts up the Docker Compose test configuration and runs both unit and e2e tests. E2e tests require a running API and database. |
| `yarn test:e2e` | Runs the e2e tests (all files that are named `e2e.test.ts`). This command requires a running API at https://127.0.0.1:3000 and a database. |
| `yarn test:unit` | Simply runs the unit tests. These are self-contained and do not require any external dependencies. |


<sup>[Back to top ^][table-of-contents]</sup>

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ services:
interval: 5s
retries: 3
start_period: 5s
test: ["CMD", "curl", "-f", "http://127.0.0.1:${APP_PORT:-3000}/versions"]
test: ["CMD", "curl", "-f", "http://127.0.0.1:${APP_PORT:-3000}/api/v1/versions"]
image: plutus/api_test
networks:
- plutus_network_test
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"setup": "./scripts/setup.sh",
"start": "./scripts/run.sh",
"test": "./scripts/test.sh",
"test:e2e": "jest --passWithNoTests --config=jest.config.e2e.ts",
"test:unit": "jest --passWithNoTests --config=jest.config.unit.ts"
"test:e2e": "APP_VERSION=$npm_package_version jest --passWithNoTests --config=jest.config.e2e.ts",
"test:unit": "APP_VERSION=$npm_package_version jest --passWithNoTests --config=jest.config.unit.ts"
},
"devDependencies": {
"@commitlint/cli": "^19.1.0",
Expand Down
12 changes: 6 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ import AppModule from '@app/modules/app/module';
import type { IEnvironmentVariables, ILogLevel } from '@app/types';

// utils
import createAPIPathPrefix from '@app/utils/createAPIPathPrefix';
import createLoggerService from '@app/utils/createLoggerService';
import parseVersion from '@app/utils/parseVersion';

(async () => {
let app: NestApplication;
let configService: ConfigService<IEnvironmentVariables, true>;
let logger: LoggerService;
let versions: string[];

try {
app = await NestFactory.create(AppModule);
Expand All @@ -30,9 +29,6 @@ import parseVersion from '@app/utils/parseVersion';
configService.get<string>(EnvironmentVariableKeyEnum.AppName),
configService.get<ILogLevel>(EnvironmentVariableKeyEnum.LogLevel)
);
versions = parseVersion(
configService.get<string>(EnvironmentVariableKeyEnum.AppVersion)
);

// setup middleware
app.useLogger(logger);
Expand All @@ -45,7 +41,11 @@ import parseVersion from '@app/utils/parseVersion';
},
})
);
app.setGlobalPrefix(`${APIPathEnum.API}/v${versions[0]}`);
app.setGlobalPrefix(
createAPIPathPrefix(
configService.get<string>(EnvironmentVariableKeyEnum.AppVersion)
)
);
app.useGlobalPipes(new ValidationPipe()); // for validating query params

// setup open api
Expand Down
6 changes: 4 additions & 2 deletions src/modules/chains/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ import { chains } from '@app/configs';
import { APIPathEnum } from '@app/enums';

// utils
import createAPIPathPrefix from '@app/utils/createAPIPathPrefix';
import mapChainConfigToChainResponseBody from '@app/utils/mapChainConfigToChainResponseBody';

describe(`/${APIPathEnum.Chains}`, () => {
const path: string = `${createAPIPathPrefix(process.env.APP_VERSION)}/${APIPathEnum.Chains}`;
let agent: Agent;

beforeAll(async () => {
agent = request(`http://127.0.0.1:3000`);
agent = request(`http://127.0.0.1:${process.env.APP_PORT}`);
});

describe(`GET /${APIPathEnum.Chains}`, () => {
it('should return the chain configuration', async () => {
const response: Response = await agent
.get(`/${APIPathEnum.Chains}`)
.get(`/${path}`)
.expect(HttpStatus.OK);

expect(response.body).toEqual(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface IProps {
integrator: string;
limit?: number;
page?: number;
}

export default class FindByIntegratorAndPageOptionsDTO {
public readonly integrator: string;
public readonly limit?: number;
public readonly page?: number;

constructor({ integrator, limit, page }: IProps) {
this.integrator = integrator;
this.limit = limit;
this.page = page;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface IProps {
total: number;
}

export default class FindByPageResultDTO {
export default class FindByIntegratorAndPageResultDTO {
public readonly data: IFeeDocument[];
public readonly limit: number;
public readonly page: number;
Expand Down
17 changes: 0 additions & 17 deletions src/modules/fee-repository/dtos/FindByPageOptionsDTO.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/modules/fee-repository/dtos/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { default as CreateOptionsDTO } from './CreateOptionsDTO';
export { default as FindByPageOptionsDTO } from './FindByPageOptionsDTO';
export { default as FindByPageResultDTO } from './FindByPageResultDTO';
export { default as FindByIntegratorAndPageOptionsDTO } from './FindByIntegratorAndPageOptionsDTO';
export { default as FindByIntegratorAndPageResultDTO } from './FindByIntegratorAndPageResultDTO';
16 changes: 8 additions & 8 deletions src/modules/fee-repository/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { FEE_PAGINATION_MAX_LIMIT } from '@app/constants';
// dtos
import {
CreateOptionsDTO,
FindByPageOptionsDTO,
FindByPageResultDTO,
FindByIntegratorAndPageOptionsDTO,
FindByIntegratorAndPageResultDTO,
} from './dtos';

// enums
Expand Down Expand Up @@ -37,16 +37,16 @@ export default class FeeRepositoryService {
return await this.model.create(dto);
}

public async findByPage({
chainId,
public async findByIntegratorAndPage({
integrator,
limit = FEE_PAGINATION_MAX_LIMIT,
page = 1,
}: FindByPageOptionsDTO): Promise<FindByPageResultDTO> {
}: FindByIntegratorAndPageOptionsDTO): Promise<FindByIntegratorAndPageResultDTO> {
const result: IFindByPageAggregateResult[] =
await this.model.aggregate<IFindByPageAggregateResult>([
{
$match: {
chainId,
integrator,
},
},
{
Expand All @@ -68,11 +68,11 @@ export default class FeeRepositoryService {
},
]);

return new FindByPageResultDTO({
return new FindByIntegratorAndPageResultDTO({
data: result[0].data,
limit,
page,
total: result[0].metadata[0].total,
total: result[0].metadata[0]?.total || 0,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { IFeeDocument } from '@app/types';

interface IFindByPageAggregateResult {
data: IFeeDocument[];
metadata: Record<'total', number>;
metadata: Record<'total', number>[];
}

export default IFindByPageAggregateResult;
76 changes: 32 additions & 44 deletions src/modules/fees/controller.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,67 @@
import {
Controller,
Get,
NotFoundException,
Param,
Query,
Req,
} from '@nestjs/common';
import { ApiNotFoundResponse, ApiOkResponse } from '@nestjs/swagger';
import { Controller, Get, Param, Query, Req } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ApiOkResponse } from '@nestjs/swagger';
import type { Request } from 'express';

// configs
import { chains } from '@app/configs';

// constants
import { FEE_PAGINATION_MAX_LIMIT } from '@app/constants';

// dtos
import { FindByPageResultDTO } from '@app/modules/fee-repository';
import { FindByIntegratorAndPageResultDTO } from '@app/modules/fee-repository';
import {
GetByChainIdOptionsDTO,
GetByIntegratorOptionsDTO,
GetFeesParamsDTO,
GetFeesQueryDTO,
GetFeesResponseBodyDTO,
} from './dtos';

// enums
import { APIPathEnum } from '@app/enums';
import { APIPathEnum, EnvironmentVariableKeyEnum } from '@app/enums';

// services
// providers
import Service from './service';

// types
import type { IChainConfig } from '@app/types';
import type { IEnvironmentVariables } from '@app/types';

// utils
import createChainId from '@app/utils/createChainId';
import createAPIPathPrefix from '@app/utils/createAPIPathPrefix';

@Controller(APIPathEnum.Fees)
export default class FeesController {
constructor(private readonly service: Service) {}
constructor(
private readonly configService: ConfigService<IEnvironmentVariables, true>,
private readonly service: Service
) {}

@Get(':chainId')
@Get(':integrator')
@ApiOkResponse({
description: 'Gets the fees collected for a given chain.',
description: 'Gets the fees collected for a given integrator.',
type: GetFeesResponseBodyDTO,
})
@ApiNotFoundResponse({
description: 'If the chain ID cannot be found.',
})
public async getByChainId(
@Param() { chainId }: GetFeesParamsDTO,
public async getByIntegrator(
@Param() { integrator }: GetFeesParamsDTO,
@Query() query: GetFeesQueryDTO,
@Req() req: Request
): Promise<GetFeesResponseBodyDTO> {
const chainConfig: IChainConfig | null =
chains.find((value) => createChainId(value) === chainId) || null;
let result: FindByPageResultDTO;

if (!chainConfig) {
throw new NotFoundException(`unknown chain "${chainId}"`);
}

result = await this.service.getByChainId(
new GetByChainIdOptionsDTO({
chainId,
limit: query.limit
? parseInt(query.limit, 10)
: FEE_PAGINATION_MAX_LIMIT,
page: query.page ? parseInt(query.page, 10) : 1,
})
);
const result: FindByIntegratorAndPageResultDTO =
await this.service.getByIntegrator(
new GetByIntegratorOptionsDTO({
integrator,
limit: query.limit
? parseInt(query.limit, 10)
: FEE_PAGINATION_MAX_LIMIT,
page: query.page ? parseInt(query.page, 10) : 1,
})
);

return new GetFeesResponseBodyDTO({
...result,
nextPageURL: `${req.protocol}://${req.get('host')}/${APIPathEnum.Fees}/${chainId}?limit=${result.limit}&page=${result.page + 1}`,
// only show the next page url
nextPageURL:
result.total > 0 && result.page < Math.ceil(result.total / result.limit)
? `${req.protocol}://${req.get('host')}/${createAPIPathPrefix(this.configService.get<string>(EnvironmentVariableKeyEnum.AppVersion))}/${APIPathEnum.Fees}/${integrator}?limit=${result.limit}&page=${result.page + 1}`
: null,
});
}
}
17 changes: 0 additions & 17 deletions src/modules/fees/dtos/GetByChainIdOptionsDTO.ts

This file was deleted.

17 changes: 17 additions & 0 deletions src/modules/fees/dtos/GetByIntegratorOptionsDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface IProps {
integrator: string;
limit: number;
page: number;
}

export default class GetByIntegratorOptionsDTO {
public readonly integrator: string;
public readonly limit: number;
public readonly page: number;

constructor({ integrator, limit, page }: IProps) {
this.integrator = integrator;
this.limit = limit;
this.page = page;
}
}
7 changes: 3 additions & 4 deletions src/modules/fees/dtos/GetFeesParamsDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { IsNotEmpty } from 'class-validator';

export default class GetFeesParamsDTO {
@ApiProperty({
description:
'The ID of the chain. This is the concatenation of the chain namespace and reference as specified in CAIP-2.',
example: 'eip155:137',
description: 'The partner that was used to collect fees.',
example: '0x34B7BEb5Bb4E6504dBa8843883796eF9CbDe0a38',
required: true,
})
@IsNotEmpty()
public readonly chainId: string;
public readonly integrator: string;
}
4 changes: 2 additions & 2 deletions src/modules/fees/dtos/GetFeesResponseBodyDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { IFeeDocument } from '@app/types';
interface IProps {
data: IFeeDocument[];
limit: number;
nextPageURL: string;
nextPageURL: string | null;
page: number;
total: number;
}
Expand All @@ -23,7 +23,7 @@ export default class GetFeesResponseBodyDTO {
@ApiProperty({
description: 'The URL to the next page.',
})
public readonly nextPageURL: string;
public readonly nextPageURL: string | null;
@ApiProperty({
description: 'The current page.',
})
Expand Down
2 changes: 1 addition & 1 deletion src/modules/fees/dtos/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as GetByChainIdOptionsDTO } from './GetByChainIdOptionsDTO';
export { default as GetByIntegratorOptionsDTO } from './GetByIntegratorOptionsDTO';
export { default as GetFeesParamsDTO } from './GetFeesParamsDTO';
export { default as GetFeesQueryDTO } from './GetFeesQueryDTO';
export { default as GetFeesResponseBodyDTO } from './GetFeesResponseBodyDTO';
Loading

0 comments on commit 8382d15

Please sign in to comment.