Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require('path');

module.exports = {
config: path.resolve('dist', 'config', 'config.json'),
'models-path': path.resolve('dist', 'db', 'models'),
'seeders-path': path.resolve('dist', 'db', 'seeders'),
'migrations-path': path.resolve('dist', 'db', 'migrations'),
};
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/sequelize": "^11.0.0",
"@nestjs/swagger": "^8.0.5",
"dotenv": "^16.4.5",
"express-basic-auth": "^1.2.1",
"geoip-lite": "^1.4.10",
"pg": "^8.13.3",
"pg-hstore": "^2.3.4",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"sequelize": "^6.37.6",
"sequelize-typescript": "^2.1.6",
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
Expand All @@ -35,6 +40,7 @@
"@types/geoip-lite": "^1.4.4",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/sequelize": "^4.28.20",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
Expand All @@ -43,6 +49,7 @@
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"sequelize-cli": "^6.6.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",
Expand Down
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const BASE_URL = '/api';
export enum URLS {
NETWORKING_CONTROLLER_URL = `${BASE_URL}/basic-utils/networking/`,
MISC_CONTROLLER_URL = `${BASE_URL}/basic-utils/misc/`,
MONETORY_CONTROLLER_URL = `${BASE_URL}/basic-utils/monetory/`,
}

export enum SUPPORTED_API_RES {
Expand Down
39 changes: 39 additions & 0 deletions src/config/database.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Dialect } from 'sequelize';
import { DbConstants } from 'src/i18n';

export class PostgresDatabaseConfig {
private readonly dbDialect: string;
constructor(
private readonly host: string,
private readonly dbPort: number,
private readonly dbUsername: string,
private readonly dbPassword: string,
private readonly databaseName: string,
) {
this.dbDialect = DbConstants.DB_DIALECT;
}

public get DB_HOST(): string {
return this.host;
}

public get DB_PORT(): number {
return this.dbPort;
}

public get DB_USERNAME(): string {
return this.dbUsername;
}

public get DB_PASSWORD(): string {
return this.dbPassword;
}

public get DB_NAME(): string {
return this.databaseName;
}

public get DB_DIALECT(): Dialect {
return this.dbDialect as Dialect;
}
}
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { PostgresDatabaseConfig } from './database.config';
import { SwaggerConf } from './swagger.config';

export const Configurations = {
Swagger: SwaggerConf,
Database: { PostgresDatabaseConfig },
};
1 change: 1 addition & 0 deletions src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export const REGEX = {
CLASS_B_IP: /^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/,
CLASS_C_IP: /^192\.168\.\d{1,3}\.\d{1,3}$/,
};
export const CURRENCY_NOTES = [500, 200, 100, 50, 20, 10, 5, 2, 1];
3 changes: 2 additions & 1 deletion src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { AppController } from './app';
import { MiscController } from './utils/misc.utils.controller';
import { MonetoryController } from './utils/monetory.utils.controller';
import { NetworkingController } from './utils/networking.utils.controller';

export const CONTROLLERS = {
AppController,
UTILS: { NetworkingController, MiscController },
UTILS: [NetworkingController, MiscController, MonetoryController],
};
18 changes: 0 additions & 18 deletions src/controllers/utils/misc.utils.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,4 @@ export class MiscController {

return reply;
}

@ApiOperation(ApiDocsConstants.UTILITIES.BASIC.MISC_ALL_CURRENCIES.ApiOpConf)
@ApiOkResponse(
ApiDocsConstants.UTILITIES.BASIC.MISC_ALL_CURRENCIES.ApiOkResConf,
)
@ApiInternalServerErrorResponse(ApiDocsConstants.COMMONS.ApiServerErrConf)
@Get('monetory/currencies')
public async getAllCurrencies(@Ip() ipAddress: string): Promise<ApiResponse> {
const reply = new ApiResponse();
const filePath = path.resolve(__dirname, '../../../', 'json/currency.json');

reply.status = ApiStatus.SUCCESS;
reply.message = 'Operation succeeded';
reply.entry_by = ipAddress || '0.0.0.0';
reply.details = await Helpers.loadJSONContent(filePath);

return reply;
}
}
74 changes: 0 additions & 74 deletions src/controllers/utils/misc.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,78 +111,4 @@ describe('/api/basic-utils/misc/', () => {
TEST_SUITES_TIMEOUT,
);
});

describe('monetory/currencies', () => {
it(
'should be defined',
() => expect(miscController).toBeDefined(),
TEST_SUITES_TIMEOUT,
);

it(
'should return currencies list with 200 status',
async () => {
const currencies = [
{ country: 'Russia', currency: 'RUB', symbol: '₽' },
{ country: 'India', currency: 'INR', symbol: '₹' },
];
jest.spyOn(Helpers, 'loadJSONContent').mockResolvedValue(currencies);

const ipAddress = '127.0.0.1';
const reply = {
status: 'success',
message: 'Operation succeeded',
entry_by: '127.0.0.1',
details: currencies,
};
const status = HttpStatus.OK;

const response = await miscController.getAllCurrencies(ipAddress);

expect(status).toBe(HttpStatus.OK);

expect(response).toBeDefined();
expect(response).toBeInstanceOf(ApiResponse);

expect(response.status).toBe(reply.status);
expect(response.message).toBe(reply.message);
expect(response.entry_by).toBe(reply.entry_by);
expect(response.details).toEqual(reply.details);
},
TEST_SUITES_TIMEOUT,
);

it(
'should return currencies list with 200 status without ip',
async () => {
const currencies = [
{ country: 'Russia', currency: 'RUB', symbol: '₽' },
{ country: 'India', currency: 'INR', symbol: '₹' },
];
jest.spyOn(Helpers, 'loadJSONContent').mockResolvedValue(currencies);

const ipAddress = '0.0.0.0';
const reply = {
status: 'success',
message: 'Operation succeeded',
entry_by: '0.0.0.0',
details: currencies,
};
const status = HttpStatus.OK;

const response = await miscController.getAllCurrencies(ipAddress);

expect(status).toBe(HttpStatus.OK);

expect(response).toBeDefined();
expect(response).toBeInstanceOf(ApiResponse);

expect(response.status).toBe(reply.status);
expect(response.message).toBe(reply.message);
expect(response.entry_by).toBe(reply.entry_by);
expect(response.details).toEqual(reply.details);
},
TEST_SUITES_TIMEOUT,
);
});
});
102 changes: 102 additions & 0 deletions src/controllers/utils/monetory.utils.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
Controller,
DefaultValuePipe,
Get,
HttpException,
HttpStatus,
Ip,
ParseIntPipe,
Query,
} from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiInternalServerErrorResponse,
ApiNotAcceptableResponse,
ApiOkResponse,
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { ApiResponse, ApiStatus, URLS } from 'src/api';
import * as path from 'path';
import { ApiActionHandlerConstants, ApiDocsConstants } from 'src/i18n';
import { Helpers } from 'src/helpers';
import { CURRENCY_NOTES } from 'src/constant';

@Controller(URLS.MONETORY_CONTROLLER_URL)
@ApiTags(ApiDocsConstants.UTILITIES.MONETORY.TAGNAME)
export class MonetoryController {
@ApiOperation(
ApiDocsConstants.UTILITIES.MONETORY.MISC_ALL_CURRENCIES.ApiOpConf,
)
@ApiOkResponse(
ApiDocsConstants.UTILITIES.MONETORY.MISC_ALL_CURRENCIES.ApiOkResConf,
)
@ApiInternalServerErrorResponse(ApiDocsConstants.COMMONS.ApiServerErrConf)
@Get('currencies')
public async getAllCurrencies(@Ip() ipAddress: string): Promise<ApiResponse> {
const reply = new ApiResponse();
const filePath = path.resolve(__dirname, '../../../', 'json/currency.json');

reply.status = ApiStatus.SUCCESS;
reply.message = 'Operation succeeded';
reply.entry_by = ipAddress || '0.0.0.0';
reply.details = await Helpers.loadJSONContent(filePath);

return reply;
}

@ApiOperation(
ApiDocsConstants.UTILITIES.MONETORY.CURRENCY_DENOMINATION.ApiOpConf,
)
@ApiOkResponse(
ApiDocsConstants.UTILITIES.MONETORY.CURRENCY_DENOMINATION.ApiOkResConf,
)
@ApiBadRequestResponse(
ApiDocsConstants.UTILITIES.MONETORY.CURRENCY_DENOMINATION.ApiBadReqResConf,
)
@ApiNotAcceptableResponse(
ApiDocsConstants.UTILITIES.MONETORY.CURRENCY_DENOMINATION
.ApiNotAcceptableResConf,
)
@ApiQuery(
ApiDocsConstants.UTILITIES.MONETORY.CURRENCY_DENOMINATION.ApiQueryConf,
)
@ApiInternalServerErrorResponse(ApiDocsConstants.COMMONS.ApiServerErrConf)
@Get(ApiActionHandlerConstants.CURRENCY_DENOMINATION)
public currencyDenomination(
@Query('amount', new DefaultValuePipe(0), new ParseIntPipe())
amount: number,
@Ip() ipAddress: string,
): ApiResponse {
const reply = new ApiResponse();
const maxAllowedAmount =
Number(process.env.MAX_ALLOWED_CURRENCY_AMT) || 1e5;

if (amount <= 0) {
reply.status = ApiStatus.VALIDATION;
reply.message = 'Operation failed';
reply.entry_by = ipAddress || '0.0.0.0';
reply.details = { msg: 'Amount should be a natural number' };

throw new HttpException(reply, HttpStatus.BAD_REQUEST);
}

if (amount > maxAllowedAmount) {
reply.status = ApiStatus.VALIDATION;
reply.message = 'Operation failed';
reply.entry_by = ipAddress || '0.0.0.0';
reply.details = { msg: 'Amount is too large' };

throw new HttpException(reply, HttpStatus.NOT_ACCEPTABLE);
}

const helpers = new Helpers();

reply.status = ApiStatus.SUCCESS;
reply.message = 'Operation succeeded';
reply.entry_by = ipAddress || '0.0.0.0';
reply.details = helpers.currencyDenomination(amount, CURRENCY_NOTES);
return reply;
}
}
Loading