Skip to content

Commit

Permalink
feat(APIM-96): added GET /yield-rates endpoint (#50)
Browse files Browse the repository at this point in the history
### Introduction
- New endpoint GET /yield-rates. It calculates UKEF exposure in months
for EW and BS Facilities
  Query parameters: searchDate (optional)

### Resolution
- GET /exposure-period uses TypeORM to get data from table
DWR_YIELD_RATE
- Old endpoint had query parameter searchDatetime, I changed it to
searchDate
- Old endpoint had field short_name, I renamed to shortName


### Miscellaneous
- Removed @ApiParam from all modules because Swagger module can generate
URL and Query parameter documentation from DTO class. We need URL and
Query parameters in DTO for validation, so we also keep it there for
documentation.
- Change MAXIMUM_TIMEZONE_LIMIT format to better match what DB response
looks, and this allows to use it for testing.
- Changed GET /exposure-period Query parameter type from date to string
date, it allows easier way to stop time input.
  • Loading branch information
avaitonis authored Mar 10, 2023
1 parent 23ba2fa commit 5f0e318
Show file tree
Hide file tree
Showing 27 changed files with 440 additions and 154 deletions.
5 changes: 4 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
"BBALIBOR",
"szenius",
"EWCS",
"NVARCHAR"
"NVARCHAR",
"ESTR",
"EESWE3",
"Curncy"
],
"dictionaries": [
"en-gb",
Expand Down
2 changes: 1 addition & 1 deletion src/constants/date.constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const DATE = {
MAXIMUM_LIMIT: '9999-12-31 00:00:00.000',
MAXIMUM_TIMEZONE_LIMIT: '9999-12-31 00:00:00.000Z',
MAXIMUM_TIMEZONE_LIMIT: '9999-12-31T00:00:00.000Z',
};
16 changes: 1 addition & 15 deletions src/modules/constants/constants.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

import { ConstantsService } from './constants.service';
import { GetConstantsSpiQueryDto } from './dto/get-constants-spi-query.dto';
Expand All @@ -18,20 +18,6 @@ export class ConstantsController {
description: 'The found record',
type: [ConstantSpiEntity],
})
@ApiParam({
name: 'oecdRiskCategory',
required: false,
type: 'int',
description: 'Country risk category. Values from 0 to 7',
example: 1,
})
@ApiParam({
name: 'category',
type: 'string',
required: false,
description: 'Constant category/type/group. Values: A, B, C, Quality of Product, Percentage of Cover',
example: 'C',
})
find(@Query() query: GetConstantsSpiQueryDto): Promise<ConstantSpiEntity[]> {
return this.constantsService.find(query.oecdRiskCategory, query.category);
}
Expand Down
12 changes: 10 additions & 2 deletions src/modules/constants/dto/get-constants-spi-query.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ export class GetConstantsSpiQueryDto {
@IsOptional()
@Min(0)
@Max(7)
@ApiProperty({ example: 1 })
@ApiProperty({
required: false,
example: 1,
description: 'Country risk category. Values from 0 to 7',
})
public oecdRiskCategory: number = null;

@IsString()
@IsOptional()
@ApiProperty({ example: 'C' })
@ApiProperty({
required: false,
example: 'C',
description: 'Constant category/type/group. Values: A, B, C, Quality of Product, Percentage of Cover',
})
@Matches(/^[a-zA-Z ]{1,20}$/)
public category: string = null;
}
30 changes: 1 addition & 29 deletions src/modules/currencies/currencies.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

import { CurrenciesService } from './currencies.service';
import { CurrencyDto, GetCurrencyExchangeDto } from './dto';
Expand Down Expand Up @@ -30,27 +30,6 @@ export class CurrenciesController {
description: 'Get the Active exchange rate',
type: GetCurrencyExchangeDto,
})
@ApiQuery({
name: 'source',
required: true,
type: 'string',
description: 'Source currency for exchange rate - Use ISO 3 alpha currency code standard. Only GBP and USD currencies are supported',
example: 'GBP',
})
@ApiQuery({
name: 'target',
required: true,
type: 'string',
description: 'Target currency for exchange rate - Use ISO 3 alpha currency code standard',
example: 'AED',
})
@ApiQuery({
name: 'exchangeRateDate',
required: false,
type: 'string',
description: 'Retrieve the exchange rate for a specific date',
example: '2021-01-26',
})
findExchangeRate(@Query() query: GetCurrencyExchangeDto): Promise<CurrencyExchangeEntity[]> {
return this.currenciesService.findExchangeRate(query.source, query.target, query.exchangeRateDate);
}
Expand All @@ -64,13 +43,6 @@ export class CurrenciesController {
description: 'Currency details based on ISO Code',
type: [CurrencyEntity],
})
@ApiParam({
name: 'isoCode',
required: true,
type: 'string',
description: 'ISO Code',
example: 'GBP',
})
findOne(@Param() param: CurrencyDto): Promise<CurrencyEntity[]> {
return this.currenciesService.findOne(param.isoCode);
}
Expand Down
15 changes: 12 additions & 3 deletions src/modules/currencies/dto/currency-exchange.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ import { IsDateString, IsISO4217CurrencyCode, IsOptional } from 'class-validator

export class GetCurrencyExchangeDto {
@IsISO4217CurrencyCode()
@ApiProperty({ example: 'GBP' })
@ApiProperty({
example: 'GBP',
description: 'Source currency for exchange rate - Use ISO 3 alpha currency code standard. Only GBP and USD currencies are supported',
})
readonly source: string;

@IsISO4217CurrencyCode()
@ApiProperty({ example: 'AED' })
@ApiProperty({
example: 'AED',
description: 'Target currency for exchange rate - Use ISO 3 alpha currency code standard',
})
readonly target: string;

@IsDateString()
@IsOptional()
@ApiProperty({ example: '2021-01-26' })
@ApiProperty({
example: '2021-01-26',
description: 'Retrieve the exchange rate for a specific date',
})
readonly exchangeRateDate: string;
}
2 changes: 1 addition & 1 deletion src/modules/currencies/dto/currency.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { IsISO4217CurrencyCode } from 'class-validator';

export class CurrencyDto {
@IsISO4217CurrencyCode()
@ApiProperty({ example: 'GBP' })
@ApiProperty({ example: 'GBP', description: 'ISO Code' })
public isoCode: string;
}
24 changes: 17 additions & 7 deletions src/modules/exposure-period/dto/get-exposure-period-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { ApiProperty } from '@nestjs/swagger';
import { ENUMS } from '@ukef/constants';
import { IsDate, IsEnum, IsString } from 'class-validator';
import { IsDateString, IsEnum, IsString, MaxLength } from 'class-validator';

export class GetExposurePeriodQueryDto {
@IsDate()
@ApiProperty({ example: '2017-07-04' })
public startdate: Date;
@IsDateString({ strict: true })
// Max length validation blocks dates with time.
@MaxLength(10, { message: '$property should use format YYYY-MM-DD' })
@ApiProperty({
example: '2017-07-04',
description: 'Guarantee commencement date for a facility',
})
public startdate: string;

@IsDate()
@ApiProperty({ example: '2018-07-04' })
public enddate: Date;
@IsDateString({ strict: true })
// Max length validation blocks dates with time.
@MaxLength(10, { message: '$property should use format YYYY-MM-DD' })
@ApiProperty({
example: '2018-07-04',
description: 'Guarantee expiry date for a facility',
})
public enddate: string;

@IsString()
@IsEnum(ENUMS.PRODUCTS)
Expand Down
23 changes: 1 addition & 22 deletions src/modules/exposure-period/exposure-period.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

import { ExposurePeriodDto } from './dto/exposure-period.dto';
import { GetExposurePeriodQueryDto } from './dto/get-exposure-period-query.dto';
Expand All @@ -18,27 +18,6 @@ export class ExposurePeriodController {
description: 'Calculated exposure period',
type: ExposurePeriodDto,
})
@ApiParam({
name: 'startdate',
required: false,
type: 'date',
description: 'Guarantee commencement date for a facility',
example: '2017-07-04',
})
@ApiParam({
name: 'enddate',
type: 'date',
required: false,
description: 'Guarantee expiry date for a facility',
example: '2018-07-04',
})
@ApiParam({
name: 'productgroup',
type: 'string',
required: false,
description: 'Facility type. It can be EW or BS',
example: 'EW',
})
find(@Query() query: GetExposurePeriodQueryDto): Promise<ExposurePeriodDto> {
return this.exposurePeriodService.calculate(query.startdate, query.enddate, query.productgroup);
}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/exposure-period/exposure-period.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ExposurePeriodService {
private readonly mdmDataSource: DataSource,
) {}

async calculate(startDate: Date, endDate: Date, productGroup: string): Promise<ExposurePeriodDto> {
async calculate(startDate: string, endDate: string, productGroup: string): Promise<ExposurePeriodDto> {
try {
// TODO: SP USP_MDM_READ_EXPOSURE_PERIOD is not using data/tables from DB. Calculation could be moved to Javascript.
const spResults = await this.mdmDataSource.query('USP_MDM_READ_EXPOSURE_PERIOD @0, @1, @2', [startDate, endDate, productGroup]);
Expand Down
7 changes: 7 additions & 0 deletions src/modules/markets/dto/markets-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsOptional } from 'class-validator';

import { QueryParamActiveEnum } from './query-param-active-enum';

export class MarketsQueryDto {
@IsEnum(QueryParamActiveEnum)
@IsOptional()
@ApiProperty({
required: false,
example: 'Y',
description: 'Optional filtering by field "active". If parameter is not provided result will include active and not active markets',
enum: QueryParamActiveEnum,
})
public active: string;
}
10 changes: 1 addition & 9 deletions src/modules/markets/markets.controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Controller, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger';

import { MarketsQueryDto } from './dto/markets-query.dto';
import { QueryParamActiveEnum } from './dto/query-param-active-enum';
import { MarketEntity } from './entities/market.entity';
import { MarketsService } from './markets.service';

Expand All @@ -13,13 +12,6 @@ export class MarketsController {
constructor(private readonly marketService: MarketsService) {}

@Get()
@ApiParam({
name: 'active',
type: 'string',
required: false,
description: 'Optional filtering by field "active". If parameter is not provided result will include active and not active markets',
enum: QueryParamActiveEnum,
})
@ApiResponse({
status: 200,
description: 'Get all markets (aka countries)',
Expand Down
7 changes: 5 additions & 2 deletions src/modules/mdm.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,36 @@ import { MarketsModule } from '@ukef/module/markets/markets.module';
import { NumbersModule } from '@ukef/module/numbers/numbers.module';
import { PremiumSchedulesModule } from '@ukef/module/premium-schedules/premium-schedules.module';
import { SectorIndustriesModule } from '@ukef/module/sector-industries/sector-industries.module';
import { YieldRatesModule } from '@ukef/module/yield-rates/yield-rates.module';

@Module({
imports: [
AuthModule,
DatabaseModule,
ConstantsModule,
CurrenciesModule,
ExposurePeriodModule,
HealthcheckModule,
InterestRatesModule,
MarketsModule,
NumbersModule,
PremiumSchedulesModule,
SectorIndustriesModule,
CurrenciesModule,
YieldRatesModule,
],
exports: [
AuthModule,
DatabaseModule,
ConstantsModule,
CurrenciesModule,
ExposurePeriodModule,
HealthcheckModule,
InterestRatesModule,
MarketsModule,
NumbersModule,
PremiumSchedulesModule,
SectorIndustriesModule,
CurrenciesModule,
YieldRatesModule,
],
})
export class MdmModule {}
7 changes: 4 additions & 3 deletions src/modules/numbers/dto/get-numbers-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsInt, IsNotEmpty, IsString, Matches, Max } from 'class-validator';
import { IsInt, IsNotEmpty, IsString, Matches, Max, Min } from 'class-validator';

export class GetNumbersQueryDto {
@IsInt()
@IsNotEmpty()
@Min(1)
@Max(9)
@ApiProperty({ example: 1 })
@ApiProperty({ example: 1, description: 'Id of UKEF ID type. Common types are: 1 for Deal/Facility, 2 for Party, 8 for Covenant' })
public type: number;

@IsString()
@IsNotEmpty()
@ApiProperty({ example: '0030052431' })
@ApiProperty({ example: '0030052431', description: 'UKEF ID to check' })
@Matches(/^\d{10}$/)
public ukefId: string;
}
20 changes: 2 additions & 18 deletions src/modules/numbers/numbers.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BadRequestException, Body, Controller, Get, ParseArrayPipe, Post, Query, UsePipes, ValidationPipe } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { BadRequestException, Body, Controller, Get, ParseArrayPipe, Post, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

import { CreateUkefIdDto } from './dto/create-ukef-id.dto';
import { GetNumbersQueryDto } from './dto/get-numbers-query.dto';
Expand All @@ -15,8 +15,6 @@ export class NumbersController {
@Post()
@ApiOperation({ summary: 'Create Number' })
@ApiBody({ type: [CreateUkefIdDto] })
@UsePipes(ValidationPipe)
@ApiResponse({ status: 201, description: 'Created.' })
create(@Body(new ParseArrayPipe({ items: CreateUkefIdDto, optional: false })) createUkefIdDtos: CreateUkefIdDto[]): Promise<UkefId[]> {
if (!createUkefIdDtos.length) {
throw new BadRequestException('Request payload is empty');
Expand All @@ -31,20 +29,6 @@ export class NumbersController {
description: 'The found record',
type: [UkefId],
})
@ApiQuery({
name: 'type',
required: true,
type: 'int',
description: 'Id of number type. Common types are: 1 for Deal/Facility, 2 for Party, 8 for Covenant',
example: 1,
})
@ApiQuery({
name: 'ukefID',
type: 'string',
required: true,
description: 'UKEF ID to check',
example: '0030052431',
})
findOne(@Query() query: GetNumbersQueryDto): Promise<UkefId> {
return this.numberService.findOne(query.type, query.ukefId);
}
Expand Down
10 changes: 1 addition & 9 deletions src/modules/premium-schedules/premium-schedules.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BadRequestException, Body, Controller, Get, Param, ParseArrayPipe, Post, Res } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Response } from 'express';

import { CreatePremiumScheduleDto } from './dto/create-premium-schedule.dto';
Expand All @@ -15,7 +15,6 @@ export class PremiumSchedulesController {
@Post('premium/schedule')
@ApiOperation({ summary: 'Create Premium Schedule sequence (aka Income exposure)' })
@ApiBody({ type: [CreatePremiumScheduleDto] })
@ApiResponse({ status: 201, description: 'Created.' })
create(
@Res({ passthrough: true }) res: Response,
@Body(new ParseArrayPipe({ items: CreatePremiumScheduleDto, optional: false })) createPremiumSchedule: CreatePremiumScheduleDto[],
Expand All @@ -33,13 +32,6 @@ export class PremiumSchedulesController {
status: 200,
type: [PremiumScheduleEntity],
})
@ApiParam({
name: 'facilityId',
required: true,
type: 'string',
description: 'UKEF facility id',
example: '10588388',
})
find(@Param() param: GetPremiumScheduleParamDto): Promise<PremiumScheduleEntity[]> {
return this.premiumSchedulesService.find(param.facilityId);
}
Expand Down
Loading

0 comments on commit 5f0e318

Please sign in to comment.