diff --git a/documentation/classes/EnvironmentVariables.html b/documentation/classes/EnvironmentVariables.html new file mode 100644 index 0000000..a89af6a --- /dev/null +++ b/documentation/classes/EnvironmentVariables.html @@ -0,0 +1,198 @@ + + +
+ + ++
+ src/infrastructure/config/env.validation.ts
+
+
+
env vatiables
+ + + + + + + + + + + + + + + +import { plainToClass } from 'class-transformer';
+import { validateSync, IsOptional } from 'class-validator';
+
+/**
+ * env vatiables
+ */
+class EnvironmentVariables {
+ // /**
+ // * Represents the amount of comission for each transaction
+ // */
+ // @IsOptional()
+ // TRANSACTION_COMMISSION = 0.001;
+
+ // @IsOptional()
+ // WIDRAW_COMMISSION = 0.001;
+
+ // @IsOptional()
+ // DEPOSIT_FEE_PER_MINUTE = 0.0001;
+}
+
+/**
+ * validates the config
+ * @param config congig
+ * @returns validated config
+ */
+export function validate(config: Record<string, unknown>) {
+ const validatedConfig = plainToClass(EnvironmentVariables, config, { enableImplicitConversion: true });
+ const errors = validateSync(validatedConfig, { skipMissingProperties: false });
+
+ if (errors.length > 0) {
+ throw new Error(errors.toString());
+ }
+ return validatedConfig;
+}
+
+ +
+ src/core/domain/dtos/es-response.dto.ts
+
+
+
Elasticsearch response DTO
+ + + + + + +
+ Properties+ |
+
+ + | +
+ + + _shards + + + | +
+ Type : object
+
+ |
+
+ Decorators :
+ +
+ @IsOptional()
+ |
+
+ Defined in src/core/domain/dtos/es-response.dto.ts:54
+ |
+
+ Contains a number of Elasticsearch shards +used for the request + |
+
+ + + hits + + + | +
+ Type : object
+
+ |
+
+ Decorators :
+ +
+ @IsOptional()
+ |
+
+ Defined in src/core/domain/dtos/es-response.dto.ts:82
+ |
+
+ Contains returned documents and metadata + |
+
+ + + timed_out + + + | +
+ Type : boolean
+
+ |
+
+ Decorators :
+ +
+ @IsDefined()
+ |
+
+ Defined in src/core/domain/dtos/es-response.dto.ts:37
+ |
+
+ Status of the request +If 'true' - the request timed out before completion + |
+
+ + + took + + + | +
+ Type : number
+
+ |
+
+ Decorators :
+ +
+ @IsDefined()
+ |
+
+ Defined in src/core/domain/dtos/es-response.dto.ts:24
+ |
+
+ Number of milliseconds it +took Elasticsearch to execute the request + |
+
import { ApiProperty } from "@nestjs/swagger";
+import { IsBoolean, IsDefined, IsNotEmpty, IsNumber, IsObject, IsOptional } from "class-validator";
+
+/**
+ * List of allowed properties in this DTO
+ */
+const allowedProperties = ['took', 'timed_out', '_shards', 'hits'];
+
+/**
+ * Elasticsearch response DTO
+ */
+export class EsResponseDto {
+ /**
+ * Number of milliseconds it
+ * took Elasticsearch to execute the request
+ */
+ @IsDefined()
+ @IsNotEmpty()
+ @IsNumber()
+ @ApiProperty({
+ description: 'took',
+ example: 5
+ })
+ took: number;
+
+ /**
+ * Status of the request
+ * If 'true' - the request timed out before completion
+ */
+ @IsDefined()
+ @IsNotEmpty()
+ @IsBoolean()
+ @ApiProperty({
+ description: 'timed_out',
+ example: false,
+ })
+ timed_out: boolean;
+
+ /**
+ * Contains a number of Elasticsearch shards
+ * used for the request
+ */
+ @IsOptional()
+ @IsObject()
+ @ApiProperty({
+ description: '_shards',
+ example: {
+ total: 1,
+ successful: 1,
+ skipped: 0,
+ failed: 0,
+ }
+ })
+ _shards: object;
+
+ /**
+ * Contains returned documents and metadata
+ */
+ @IsOptional()
+ @IsObject()
+ @ApiProperty({
+ description: 'hits',
+ example: {
+ total: {
+ value: 3,
+ relation: 'eq'
+ },
+ max_score: 1.2,
+ hits: [{
+ _index: 'papers',
+ _id: '01002',
+ _score: 1.2,
+ _source: {
+
+ },
+ fields: {
+
+ }
+ }],
+ }
+ })
+ hits: object;
+}
+ +
+ src/core/exceptions/http-response.exception.ts
+
+
+
implements http exception with http response from the service of common module
+ + + ++
+ HttpException
+
+constructor(data: HttpResponse)
+ |
+ ||||||||
+ + | +||||||||
+ Http response exception contructor +
+ Parameters :
+
+
|
+
import { HttpException } from '@nestjs/common';
+import { HttpResponse } from '../domain/interfaces';
+
+//==================================================================================================
+/**
+ * implements http exception with http response from the service of common module
+ */
+export class HttpResponseException extends HttpException {
+ /**
+ * Http response exception contructor
+ * @param data Http response
+ */
+ constructor(data: HttpResponse) {
+ super(HttpException.createBody(data, data.description, data.status), data.status);
+ }
+}
+
+//==================================================================================================
+
+ +
+ src/core/domain/dtos/page.dto.ts
+
+
+
Page model for pagination
+ + + + + + +
+ Properties+ |
+
+ + | +
+constructor(data: T[], meta: PageMeta)
+ |
+ |||||||||
+ Defined in src/core/domain/dtos/page.dto.ts:31
+ |
+ |||||||||
+ Constructs an object with provided parameters +
+ Parameters :
+
+
|
+
+ + + Readonly + data + + + | +
+ Type : T[]
+
+ |
+
+ Decorators :
+ +
+ @IsArray()
+ |
+
+ Defined in src/core/domain/dtos/page.dto.ts:22
+ |
+
+ Data block of the page + |
+
+ + + Readonly + meta + + + | +
+ Type : PageMeta
+
+ |
+
+ Decorators :
+ +
+ @ApiProperty({description: 'Metadata for the page'})
+ |
+
+ Defined in src/core/domain/dtos/page.dto.ts:31
+ |
+
+ Metadata of the page + |
+
import { ApiProperty } from "@nestjs/swagger";
+import { IsArray } from "class-validator";
+import { PageMeta } from "../interfaces/page-meta.interface";
+
+/**
+ * List of allowed properties in this DTO
+ */
+const allowedProperties = ['data', 'meta'];
+
+/**
+ * Page model for pagination
+ */
+export class PageDto<T> {
+ /**
+ * Data block of the page
+ */
+ @IsArray()
+ @ApiProperty({
+ description: 'All data the page contains',
+ isArray: true,
+ })
+ readonly data: T[];
+
+ /**
+ * Metadata of the page
+ */
+ @ApiProperty({
+ description: 'Metadata for the page',
+ // example: [],
+ })
+ readonly meta: PageMeta;
+
+ /**
+ * Constructs an object with provided parameters
+ * @param data
+ * @param meta
+ */
+ constructor(data: T[], meta: PageMeta) {
+ this.data = data;
+ this.meta = meta;
+ }
+}
+ +
+ src/core/domain/dtos/search-q.dto.ts
+
+
+
Elasticsearch response DTO
+ + + + + + +
+ Properties+ |
+
+ + | +
+ + + limit + + + | +
+ Type : number
+
+ |
+
+ Decorators :
+ +
+ @IsOptional()
+ |
+
+ Defined in src/core/domain/dtos/search-q.dto.ts:47
+ |
+
+ Limits the number of displayed elements. + |
+
+ + + order + + + | +
+ Type : string
+
+ |
+
+ Decorators :
+ +
+ @IsOptional()
+ |
+
+ Defined in src/core/domain/dtos/search-q.dto.ts:58
+ |
+
+ Limits the number of displayed elements. + |
+
+ + + page + + + | +
+ Type : number
+
+ |
+
+ Decorators :
+ +
+ @IsDefined()
+ |
+
+ Defined in src/core/domain/dtos/search-q.dto.ts:36
+ |
+
+ Page number to display. + |
+
+ + + query + + + | +
+ Type : string
+
+ |
+
+ Decorators :
+ +
+ @IsDefined()
+ |
+
+ Defined in src/core/domain/dtos/search-q.dto.ts:24
+ |
+
+ Given query string to perform the +search on. + |
+
import { ApiProperty } from "@nestjs/swagger";
+import { IsDefined, IsIn, IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator";
+
+/**
+ * List of allowed properties in this DTO
+ */
+const allowedProperties = ['query', 'pagen', 'limit', 'order'];
+
+/**
+ * Elasticsearch response DTO
+ */
+export class SearchQueryDto {
+ /**
+ * Given query string to perform the
+ * search on.
+ */
+ @IsDefined()
+ @IsNotEmpty()
+ @IsString()
+ @ApiProperty({
+ description: 'query',
+ example: 'Particle Accelerator'
+ })
+ query: string;
+
+ /**
+ * Page number to display.
+ */
+ @IsDefined()
+ @IsNotEmpty()
+ @IsInt()
+ @ApiProperty({
+ description: 'page',
+ example: 3,
+ })
+ page: number;
+
+ /**
+ * Limits the number of displayed elements.
+ */
+ @IsOptional()
+ @IsInt()
+ @ApiProperty({
+ description: 'limit',
+ example: 10,
+ })
+ limit: number;
+
+ /**
+ * Limits the number of displayed elements.
+ */
+ @IsOptional()
+ @IsString()
+ @ApiProperty({
+ description: 'order',
+ example: 'asc',
+ })
+ order: string;
+}
+ +
+ src/core/domain/dtos/search-result.dto.ts
+
+
+
Elasticsearch response DTO
+ + + + + + +
+ Properties+ |
+
+ + | +
+constructor(code: number, data: object)
+ |
+
+ Defined in src/core/domain/dtos/search-result.dto.ts:37
+ |
+
+ Constructs an object with provided parameters + |
+
+ + + data + + + | +
+ Type : object
+
+ |
+
+ Decorators :
+ +
+ @IsDefined()
+ |
+
+ Defined in src/core/domain/dtos/search-result.dto.ts:37
+ |
+
+ All the data acquired. + |
+
+ + + status + + + | +
+ Type : number
+
+ |
+
+ Decorators :
+ +
+ @IsDefined()
+ |
+
+ Defined in src/core/domain/dtos/search-result.dto.ts:23
+ |
+
+ Status code + |
+
import { ApiProperty } from "@nestjs/swagger";
+import { IsArray, IsDefined, IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator";
+
+/**
+ * List of allowed properties in this DTO
+ */
+const allowedProperties = ['data', 'status'];
+
+/**
+ * Elasticsearch response DTO
+ */
+export class SearchResultDto {
+ /**
+ * Status code
+ */
+ @IsDefined()
+ @IsNotEmpty()
+ @IsInt()
+ @ApiProperty({
+ description: 'Status code',
+ example: 200,
+ })
+ status: number;
+
+ /**
+ * All the data acquired.
+ */
+ @IsDefined()
+ @IsNotEmpty()
+ @IsArray()
+ @ApiProperty({
+ description: 'Data acquired from the Elasticsearch',
+ example: {
+
+ },
+ })
+ data: object;
+
+ /**
+ * Constructs an object with provided parameters
+ * @param code
+ * @param data
+ */
+ constructor(code: number, data: object) {
+ this.status = code;
+ this.data = data;
+ }
+}
+ +
+ src/application/controller/health.controller.ts
+
+
+ health
+
+
+
Health controller class
+ + + + + + +
+ Methods+ |
+
+
|
+
import { Controller, Get } from '@nestjs/common';
+import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus';
+/**
+ * Health controller class
+ */
+@Controller('health')
+export class HealthController {
+ /**
+ * Health check controller class constructor.
+ * @param health health check service
+ * @param http http response
+ */
+ constructor(private health: HealthCheckService, private http: HttpHealthIndicator) {}
+ //======================================================================================================
+ /**
+ * Checks the liveness of the project
+ * @returns http response
+ */
+ @Get()
+ @HealthCheck()
+ check() {
+ return { status: 'ok', info: { alive: { status: 'up' } }, error: {}, details: { alive: { status: 'up' } } };
+ }
+}
+
+ +
+ src/application/controller/papers.controller.ts
+
+
+ papers
+
+
+
/papers/ route controller
+ + + + + + +
+ Methods+ |
+
+
|
+
+ + + getByContext + + + | +||||
+getByContext(query)
+ |
+ ||||
+ Decorators :
+ + @ApiOperation({summary: 'Finds papers by context based on the query.'})
+ |
+ ||||
+ + | +||||
+ Request handler for: GET /papers/search +
+ Parameters :
+
+
+
+
+
+ Returns :
+ object
+
+
+
+ a response with a set of matching papers + + |
+
+ + + getByID + + + | +||||||
+getByID(uuid: string)
+ |
+ ||||||
+ Decorators :
+ + @ApiOperation({summary: 'Finds paper by its UUID.'})
+ |
+ ||||||
+ + | +||||||
+ Request handler for GET /papers/{uuid} +
+ Parameters :
+
+
+
+
+
+ Returns :
+ object
+
+
+
+ a response with a requested object + + |
+
import { Controller, Get, HttpCode, HttpException, Next, Param, ParseUUIDPipe, Put, Query, Req, Res, UseInterceptors } from "@nestjs/common";
+import { SearchService } from "../../core/services/common/search.service";
+import { Response } from "express";
+import { PageInterceptor } from "src/core/interceptors/page.interceptor";
+import { LoggerInterceptor } from "src/core/interceptors";
+import { SearchResultDto } from "src/core/domain/dtos/search-result.dto";
+import { ApiOperation, ApiResponse } from "@nestjs/swagger";
+
+/**
+ * /papers/ route controller
+ */
+@Controller('papers')
+export class PapersController {
+ constructor(private searchService: SearchService) {}
+
+ /**
+ * Request handler for: GET /papers/search
+ * @param query
+ * @param response
+ * @returns a response with a set of matching papers
+ */
+ @ApiOperation({ summary: 'Finds papers by context based on the query.' })
+ @ApiResponse({
+ status: 200,
+ description: 'Returns back acquired papers.',
+ type: SearchResultDto,
+ })
+ @Get('search')
+ @UseInterceptors(PageInterceptor)
+ @HttpCode(200)
+ getByContext(@Query() query): object {
+ return this.searchService.findByContext(query.query).then(
+ (response: SearchResultDto) => {
+ // console.log(JSON.stringify(response.data, null, 2));
+ return response.data;
+ },
+ (error: SearchResultDto) => {
+ throw new HttpException(error.data, error.status);
+ }
+ );
+ }
+
+ /**
+ * Request handler for GET /papers/{uuid}
+ * @param uuid
+ * @param response
+ * @returns a response with a requested object
+ */
+ @ApiOperation({ summary: 'Finds paper by its UUID.' })
+ @ApiResponse({
+ status: 200,
+ description: 'Returns back acquired paper.',
+ type: SearchResultDto,
+ })
+ @Get(':uuid')
+ getByID(@Param('uuid', ParseUUIDPipe) uuid: string): object {
+ return this.searchService.findByID(uuid).then(
+ (response) => {
+ // console.log(JSON.stringify(response.data, null, 2));
+ return response.data;
+ },
+ (error) => {
+ throw new HttpException(error.data, error.status);
+ }
+ );
+ }
+}
+ File | +Type | +Identifier | +Statements | +
---|---|---|---|
+ + src/application/controller/health.controller.ts + | +controller | +HealthController | ++ 100 % + (2/2) + | +
+ + src/application/controller/papers.controller.ts + | +controller | +PapersController | ++ 100 % + (3/3) + | +
+ + src/core/decorators/public.decorator.ts + | +variable | +IS_PUBLIC_KEY | ++ 100 % + (1/1) + | +
+ + src/core/decorators/public.decorator.ts + | +variable | +Public | ++ 100 % + (1/1) + | +
+ + src/core/decorators/roles.decorator.ts + | +variable | +Roles | ++ 100 % + (1/1) + | +
+ + src/core/decorators/roles.decorator.ts + | +variable | +ROLES_KEY | ++ 100 % + (1/1) + | +
+ + src/core/domain/dtos/es-response.dto.ts + | +class | +EsResponseDto | ++ 100 % + (5/5) + | +
+ + src/core/domain/dtos/es-response.dto.ts + | +variable | +allowedProperties | ++ 100 % + (1/1) + | +
+ + src/core/domain/dtos/page.dto.ts + | +class | +PageDto | ++ 100 % + (4/4) + | +
+ + src/core/domain/dtos/page.dto.ts + | +variable | +allowedProperties | ++ 100 % + (1/1) + | +
+ + src/core/domain/dtos/search-q.dto.ts + | +class | +SearchQueryDto | ++ 100 % + (5/5) + | +
+ + src/core/domain/dtos/search-q.dto.ts + | +variable | +allowedProperties | ++ 100 % + (1/1) + | +
+ + src/core/domain/dtos/search-result.dto.ts + | +class | +SearchResultDto | ++ 100 % + (4/4) + | +
+ + src/core/domain/dtos/search-result.dto.ts + | +variable | +allowedProperties | ++ 100 % + (1/1) + | +
+ + src/core/domain/interfaces/http-response.interface.ts + | +interface | +HttpResponse | ++ 100 % + (6/6) + | +
+ + src/core/domain/interfaces/page-meta.interface.ts + | +interface | +PageMeta | ++ 100 % + (6/6) + | +
+ + src/core/exceptions/http-response.exception.ts + | +class | +HttpResponseException | ++ 100 % + (2/2) + | +
+ + src/core/guards/roles.guard.ts + | +guard | +RolesGuard | ++ 100 % + (3/3) + | +
+ + src/core/helpers/env.helper.ts + | +function | +expandEnvVariables | ++ 100 % + (1/1) + | +
+ + src/core/helpers/util.helper.ts + | +function | +naiveRound | ++ 100 % + (1/1) + | +
+ + src/core/helpers/util.helper.ts + | +function | +processHttpError | ++ 100 % + (1/1) + | +
+ + src/core/helpers/util.helper.ts + | +function | +processMicroserviceHttpError | ++ 100 % + (1/1) + | +
+ + src/core/helpers/util.helper.ts + | +function | +validateDTO | ++ 100 % + (1/1) + | +
+ + src/core/helpers/util.helper.ts + | +function | +validateOutputDTO | ++ 100 % + (1/1) + | +
+ + src/core/interceptors/logger.interceptor.ts + | +injectable | +LoggerInterceptor | ++ 100 % + (4/4) + | +
+ + src/core/interceptors/page.interceptor.ts + | +injectable | +PageInterceptor | ++ 100 % + (2/2) + | +
+ + src/core/pipes/validation.pipe.ts + | +interface | +ValidationPipeOptions | ++ 100 % + (4/4) + | +
+ + src/core/services/common/http-response.service.ts + | +injectable | +HttpResponseService | ++ 100 % + (5/5) + | +
+ + src/core/services/common/logger.service.ts + | +injectable | +LoggerService | ++ 100 % + (11/11) + | +
+ + src/core/services/common/search.service.ts + | +injectable | +SearchService | ++ 100 % + (5/5) + | +
+ + src/infrastructure/config/env.objects.ts + | +interface | +VirtualBankOptions | ++ 100 % + (4/4) + | +
+ + src/infrastructure/config/env.objects.ts + | +variable | +configuration | ++ 100 % + (1/1) + | +
+ + src/infrastructure/config/env.validation.ts + | +class | +EnvironmentVariables | ++ 100 % + (1/1) + | +
+ + src/infrastructure/config/env.validation.ts + | +function | +validate | ++ 100 % + (1/1) + | +
+ + src/infrastructure/modules/app.module.ts + | +variable | +modulesList | ++ 100 % + (1/1) + | +
+ + src/main.ts + | +function | +bootstrap | ++ 100 % + (1/1) + | +
+
+ src/core/guards/roles.guard.ts
+
+
+
roles guard
+ + + + + + +
+ Methods+ |
+
+
|
+
+constructor(reflector: Reflector)
+ |
+ ||||||||
+ Defined in src/core/guards/roles.guard.ts:9
+ |
+ ||||||||
+ contructs the role guard service +
+ Parameters :
+
+
|
+
+ + + canActivate + + + | +||||||||
+canActivate(context: ExecutionContext)
+ |
+ ||||||||
+ Defined in src/core/guards/roles.guard.ts:23
+ |
+ ||||||||
+ checks if the user has allowed permission (role) +
+ Parameters :
+
+
+
+
+
+ Returns :
+ boolean
+
+
+
+ returns true if the user has appropriate role + + |
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
+import { Reflector } from '@nestjs/core';
+import { Roles as Role } from '..//domain/enums';
+import { ROLES_KEY } from '../decorators';
+/**
+ * roles guard
+ */
+@Injectable()
+export class RolesGuard implements CanActivate {
+ //==================================================================================================
+ /**
+ * contructs the role guard service
+ * @param reflector reflector of the guard
+ */
+ constructor(private reflector: Reflector) {}
+
+ //==================================================================================================
+ /**
+ * checks if the user has allowed permission (role)
+ * @param context context of the guard (actual information)
+ * @returns returns true if the user has appropriate role
+ */
+ canActivate(context: ExecutionContext): boolean {
+ const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
+ context.getHandler(),
+ context.getClass(),
+ ]);
+ if (!requiredRoles) {
+ return true;
+ }
+
+ const { user } = context.switchToHttp().getRequest();
+
+ return user.roles.some((role: Role) => requiredRoles.includes(role));
+ }
+
+ //==================================================================================================
+}
+
+ The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation.
+git clone https://github.com/MoeidHeidari/nestjs-boilerplate
+cd monetary-transaction
There are different stages of building the application for this service. Based on the environment you want to deploy we have different ways to build the application. following information may help with building the service.
+npm install
+
+npm run build
+
+npm run test:ci
+
+npm start:{dev || debug || prod}
cd scripts
+
+bash run.sh -h
+
+2022.05.30.14.43
+
+Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-buildDocker] [-runDocker] [-runApp] [-runDoc] [-packageHelm]
+
+This script helps you to run the application in different forms. below you can get the full list of available options.
+
+Available options:
+
+-h, --help Print this help and exit
+
+-buildDocker Build the docker image called "imageName:latest"
+
+-runDocker Build the docker image and run on local machine
+
+-runApp Run application with npm in usual way for development
+
+-runDoc Generate the code documentation
+
+-packageHelm makes a helm package from the helm chart.
with the following instruction you can install the helm chart on an up and running kubernetes cluster.
+cd k8s
+
+helm install {sample-app} {app-0.1.0.tgz} --set service.type=NodePort
Alternativelly you can deploy the application on an up an running kubernetes cluster using provided config files.
+cd k8s/configFiles
+kubectl apply -f app-namespace.yaml, app-configmap.yaml, app-deployment.yaml, app-service.yaml
it should give you following output
+namespace/app created
+configmap/app-config created
+deployment.apps/app created
+service/app created
by calling the following endpoint you can make sure that the application is running and listening to your desired port
+http://localhost:{port_number}/health
most probably you will get a result back as follow
+++Example
+
++{"status":"ok","info":{"alive":{"status":"up"}},"error":{},"details":{"alive":{"status":"up"}}}
+
mertics
+to get the default metrics of the application you can use the following endpoint
+http://localhost:{port_number}/metrics
by calling the following endpoint you can see the Swagger OpenApi documentation and explore all the available apis and schemas.
+http://localhost:{port_number}/api
By running following comman you can generate the full code documentation (Compodoc) and get access to it through port 7000
npm run doc
+
+ src/core/services/common/http-response.service.ts
+
+
+
HTTP response service
+ + + + + +
+ Methods+ |
+
+
|
+
+ + + generate + + + | +|||||||||||||||||||||||||
+generate(status: number, data, message: string, description: string)
+ |
+ |||||||||||||||||||||||||
+ + | +|||||||||||||||||||||||||
+ generates the HTTP response +
+ Parameters :
+
+
+
+
+
+ Returns :
+ HttpResponse
+
+
+
+ response + + |
+
+ + + Private + getDescription + + + | +||||||||
+
+ getDescription(status: number)
+ |
+ ||||||||
+ + | +||||||||
+ gets the description +
+ Parameters :
+
+
+
+
+
+ Returns :
+ string
+
+
+
+ description + + |
+
+ + + Private + getMessage + + + | +||||||||
+
+ getMessage(status: number)
+ |
+ ||||||||
+ + | +||||||||
+ gets the message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ string
+
+
+
+ message + + |
+
+ + + Private + getType + + + | +||||||||
+
+ getType(status: number)
+ |
+ ||||||||
+ + | +||||||||
+ gets the type +
+ Parameters :
+
+
+
+
+
+ Returns :
+ string
+
+
+
+ type + + |
+
import { HttpStatus, Injectable } from '@nestjs/common';
+import {
+ HttpResponseDescriptions,
+ HttpResponseMessages,
+ HttpResponseTypes,
+ HttpResponseTypesCodes,
+} from '../../domain/enums'
+
+import { HttpResponse } from '../../domain/interfaces';
+
+/**
+ * HTTP response service
+ */
+@Injectable()
+export class HttpResponseService {
+ //==================================================================================================
+ /**
+ * gets the message
+ * @param status HTTP status
+ * @returns message
+ */
+ private getMessage(status: number): string {
+ return HttpResponseMessages[HttpStatus[status].toString() as keyof typeof HttpResponseMessages];
+ }
+
+ //==================================================================================================
+ /**
+ * gets the description
+ * @param status HTTP status
+ * @returns description
+ */
+ private getDescription(status: number): string {
+ return HttpResponseDescriptions[HttpStatus[status].toString() as keyof typeof HttpResponseMessages];
+ }
+
+ //==================================================================================================
+ /**
+ * gets the type
+ * @param status HTTP status
+ * @returns type
+ */
+ private getType(status: number): string {
+ return HttpResponseTypes[
+ HttpResponseTypesCodes[Math.floor(status / 100)].toString() as keyof typeof HttpResponseTypes
+ ];
+ }
+
+ //==================================================================================================
+ /**
+ * generates the HTTP response
+ * @param status HTTP status
+ * @param data data
+ * @param message custom message
+ * @param description custom description
+ * @returns response
+ */
+ generate(
+ status: number,
+ data: unknown = {},
+ message: string = this.getMessage(status),
+ description: string = this.getDescription(status)
+ ): HttpResponse {
+ const response: HttpResponse = {
+ type: this.getType(status),
+ status: status,
+ message: message,
+ description: description,
+ data: data,
+ };
+
+ return response;
+ }
+}
+
+ +
+ src/core/interceptors/logger.interceptor.ts
+
+
+
Logs the requests
+ + + + + +
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ + + Private + logHttpRequest + + + | +||||||||||||
+
+ logHttpRequest(context: ExecutionContext, startTime: number)
+ |
+ ||||||||||||
+ + | +||||||||||||
+ logs the HTTP requests +
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+ nothing + + |
+
+ + + Private + Readonly + logger + + + | +
+ Type : LoggerService
+
+ |
+
+ Default value : new LoggerService(LoggerInterceptor.name)
+ |
+
+ + | +
+ logs requests for the service + |
+
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
+import { Observable } from 'rxjs';
+import { tap } from 'rxjs/operators';
+import { Request, Response } from 'express';
+import { LoggerService } from '../services/common'
+////////////////////////////////////////////////////////////////////////
+/**
+ * Logs the requests
+ */
+@Injectable()
+export class LoggerInterceptor implements NestInterceptor {
+ //==================================================================================================
+ /**
+ * logs requests for the service
+ */
+ private readonly logger: LoggerService = new LoggerService(LoggerInterceptor.name);
+
+ //==================================================================================================
+ /**
+ * intercept handler
+ * @param context context
+ * @param next next call
+ * @returns handler
+ */
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
+ const startTime = Date.now();
+ const contextType = context.getType();
+
+ return next.handle().pipe(
+ tap(
+ () => {
+ if (contextType === 'http') {
+ this.logHttpRequest(context, startTime);
+ }
+ },
+ (error: Error) => {
+ if (contextType === 'http') {
+ this.logHttpRequest(context, startTime);
+ } else {
+ const reqTime = Date.now() - startTime;
+ this.logger.log(`[${error.name}] ${error.message} ${reqTime}ms`);
+ }
+ }
+ )
+ );
+ }
+
+ //==================================================================================================
+ /**
+ * logs the HTTP requests
+ * @param context context
+ * @param startTime start time
+ * @returns nothing
+ */
+ private logHttpRequest(context: ExecutionContext, startTime: number) {
+ if (context.getType() !== 'http') return;
+ const reqTime = Date.now() - startTime;
+ const controllerName = context.getClass().name;
+ const handlerName = context.getHandler().name;
+ const request = context.switchToHttp().getRequest<Request>();
+ const response = context.switchToHttp().getResponse<Response>();
+ const { url, method } = request;
+ const { statusCode } = response;
+ this.logger.log(
+ `[HTTP] ${method.toUpperCase()} ${url} ${statusCode} [${controllerName}:${handlerName}] ${reqTime}ms`
+ );
+ }
+}
+
+ +
+ src/core/services/common/logger.service.ts
+
+
+
service for logging
+ + + + + +
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+ + | +
+constructor(context: string)
+ |
+ ||||||
+ + | +||||||
+ constructor for the logger +
+ Parameters :
+
+
|
+
+ + + Static + createlogger + + + | +||||||||
+
+ createlogger(context: string)
+ |
+ ||||||||
+ + | +||||||||
+ creates the logger +
+ Parameters :
+
+
+
+
+
+ Returns :
+ LoggerService
+
+
+
+ logger + + |
+
+ + + Public + debug + + + | +||||||||||||
+
+ debug(message: string, ...args: any[])
+ |
+ ||||||||||||
+ + | +||||||||||||
+ logs the debug message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Public + error + + + | +||||||||||||||||
+
+ error(message: string, error?: string | Error, ...args: any[])
+ |
+ ||||||||||||||||
+ + | +||||||||||||||||
+ logs the error message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Private + format + + + | +||||||||||||
+
+ format(message: string, args?: string[])
+ |
+ ||||||||||||
+ + | +||||||||||||
+ formats the message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ any
+
+
+
+ formatted message + + |
+
+ + + Public + log + + + | +||||||||||||
+
+ log(message: string, ...args: any[])
+ |
+ ||||||||||||
+ + | +||||||||||||
+ logs the message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Public + verbose + + + | +||||||||||||
+
+ verbose(message: string, ...args: any[])
+ |
+ ||||||||||||
+ + | +||||||||||||
+ logs the verbose message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Public + warn + + + | +||||||||||||
+
+ warn(message: string, ...args: any[])
+ |
+ ||||||||||||
+ + | +||||||||||||
+ logs the warning message +
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Private + Readonly + Optional + context + + + | +
+ Type : string
+
+ |
+
+ + | +
+ context + |
+
+ + + Private + Readonly + logger + + + | +
+ Type : Logger
+
+ |
+
+ + | +
+ logger + |
+
import { Injectable, Logger, LoggerService as NestLoggerService } from '@nestjs/common';
+import { formatWithOptions } from 'util';
+
+/**
+ * service for logging
+ */
+@Injectable()
+export class LoggerService implements NestLoggerService {
+ /**
+ * logger
+ */
+ private readonly logger: Logger;
+ /**
+ * context
+ */
+ private readonly context?: string;
+ //=============================================================================================================
+ /**
+ * constructor for the logger
+ * @param context
+ */
+ constructor(context: string) {
+ this.logger = new Logger(context);
+ this.context = context;
+ }
+ //=============================================================================================================
+ /**
+ * creates the logger
+ * @param context context
+ * @returns logger
+ */
+ static createlogger(context: string): LoggerService {
+ return new LoggerService(context);
+ }
+ //=============================================================================================================
+ /**
+ * logs the message
+ * @param message message
+ * @param args arguments
+ */
+ public log(message: string, ...args: any[]) {
+ this.logger.log(this.format(message, args));
+ }
+ //=============================================================================================================
+ /**
+ * logs the error message
+ * @param message message
+ * @param error error
+ * @param args arguments
+ */
+ public error(message: string, error?: string | Error, ...args: any[]) {
+ this.logger.error(this.format(message, args), error instanceof Error ? error.stack : error);
+ }
+ //=============================================================================================================
+ /**
+ * logs the warning message
+ * @param message message
+ * @param args arguments
+ */
+ public warn(message: string, ...args: any[]) {
+ this.logger.warn(this.format(message, args));
+ }
+ //=============================================================================================================
+ /**
+ * logs the debug message
+ * @param message message
+ * @param args arguments
+ */
+ public debug(message: string, ...args: any[]) {
+ this.logger.debug(this.format(message, args));
+ }
+ //=============================================================================================================
+ /**
+ * logs the verbose message
+ * @param message message
+ * @param args arguments
+ */
+ public verbose(message: string, ...args: any[]) {
+ this.logger.verbose(this.format(message, args));
+ }
+ //=============================================================================================================
+ /**
+ * formats the message
+ * @param message message
+ * @param args arguments
+ * @returns formatted message
+ */
+ private format(message: string, args?: string[]) {
+ if (!args || !args.length) return message;
+
+ return formatWithOptions({ colors: true, depth: 5 }, message, ...args);
+ }
+ //=============================================================================================================
+}
+
+ +
+ src/core/interceptors/page.interceptor.ts
+
+
+
Pagination-implementing interceptor
+ + + + + +
+ Methods+ |
+
+
|
+
+ + + intercept + + + | +|||||||||
+intercept(context: ExecutionContext, next: CallHandler
+ |
+ |||||||||
+ Defined in src/core/interceptors/page.interceptor.ts:20
+ |
+ |||||||||
+ Override of intercept() method, specified in NestInterceptor interface +
+ Parameters :
+
+
+
+
+
+ Returns :
+ Observable | Promise
+
+
+
+ Page with content and metadata + + |
+
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
+import { MetadataScanner } from "@nestjs/core";
+import { Observable, map } from "rxjs";
+import { PageDto } from "../domain/dtos";
+import { SearchQueryDto } from "../domain/dtos/search-q.dto";
+import { Order } from "../domain/enums/page-order.enum";
+import { PageMeta } from "../domain/interfaces";
+
+/**
+ * Pagination-implementing interceptor
+ */
+@Injectable()
+export class PageInterceptor implements NestInterceptor {
+ /**
+ * Override of intercept() method, specified in NestInterceptor interface
+ * @param context
+ * @param next
+ * @returns Page with content and metadata
+ */
+ intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
+ const request = context.switchToHttp().getRequest();
+ const query: SearchQueryDto = request.query;
+
+ return next.handle().pipe(
+ map((res) => {
+ if (!res.hits) return res;
+
+ let meta: PageMeta = {
+ pagenum: +query?.page,
+ order: query?.order?.toUpperCase() === Order.ASC ? Order.ASC : Order.DESC,
+ hasNext: false,
+ hasPrev: false,
+ pagesize: !query?.limit ? 1 : query.limit,
+ };
+
+ meta.hasNext = res.hits[meta.pagenum * meta.pagesize] ? true : false;
+ meta.hasPrev = res.hits[(meta.pagenum - 1) * meta.pagesize - 1] ? true: false;
+
+ const data = res.hits.slice((meta.pagenum - 1) * meta.pagesize, meta.pagenum * meta.pagesize);
+
+ return new PageDto(data, meta);
+ })
+ );
+ }
+
+ // getQueryParams(str: string): any {
+ // let parameters: object = {};
+ // let pairs: string[] = str.split(',');
+ // parameters['main'] = pairs[0];
+ // pairs.shift();
+
+ // if(!pairs || pairs[0] === '') return parameters;
+
+ // for (const pair of pairs) {
+ // const key: string = pair.substring(0, pair.indexOf('='));
+ // const value: string = pair.substring(pair.indexOf('=') + 1);
+ // parameters[key] = value;
+ // }
+
+ // return parameters;
+ // }
+}
+ +
+ src/core/services/common/search.service.ts
+
+
+
Search service provider
+ + + + + +
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(httpService: HttpService)
+ |
+ ||||||
+ + | +||||||
+ Constructs the service with injection of +HTTPService instance +
+ Parameters :
+
+
|
+
+ + + Async + findByContext + + + | +||||||
+
+ findByContext(query_str: string)
+ |
+ ||||||
+ + | +||||||
+ Finds relevant documents by context using the given query string +
+ Parameters :
+
+
+
+
+
+ Returns :
+ Promise<SearchResultDto>
+
+
+
+ Elasticsearch hits or an error object + + |
+
+ + + Async + findByID + + + | +||||||
+
+ findByID(uuid: string)
+ |
+ ||||||
+ + | +||||||
+ Finds a paper by its own ID +
+ Parameters :
+
+
+
+
+
+ Returns :
+ Promise<SearchResultDto>
+
+
+
+ Elasticsearch hits or an error object + + |
+
+ + + Private + Readonly + ES_PORT + + + | +
+ Default value : process.env.ES_PORT
+ |
+
+ + | +
+ Elastichsearch server port-number + |
+
import { HttpService } from "@nestjs/axios";
+import { Injectable } from "@nestjs/common";
+import { map, take } from "rxjs";
+import { EsResponseDto } from "src/core/domain/dtos";
+import { SearchResultDto } from "src/core/domain/dtos/search-result.dto";
+
+/**
+ * Search service provider
+ */
+@Injectable()
+export class SearchService {
+ /**
+ * Constructs the service with injection of
+ * HTTPService instance
+ * @param httpService
+ */
+ constructor(private readonly httpService: HttpService) {}
+
+ /**
+ * Elastichsearch server port-number
+ */
+ private readonly ES_PORT = process.env.ES_PORT;
+
+ /**
+ * Finds a paper by its own ID
+ * @param uuid
+ * @returns Elasticsearch hits or an error object
+ */
+ async findByID(uuid: string): Promise<SearchResultDto> { // Should I change 'object' to specific DTO?
+ let es_query = {
+ query: {
+ query_string: {
+ query: 'id:' + uuid
+ }
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ (this.httpService.get<EsResponseDto>('http://localhost:' + this.ES_PORT + '/_search', {
+ data: es_query,
+ headers: {'Content-Type': 'application/json'},
+ }))
+ .pipe(take(1), map(axiosRes => axiosRes.data))
+ .subscribe((res: any) => {
+ if (res.timed_out) {
+ reject(new SearchResultDto(504, {message: 'Timed Out'}));
+ }
+
+ if (!res.hits.hits.length) {
+ reject(new SearchResultDto(404, {message: 'Not Found'}));
+ }
+
+ resolve(new SearchResultDto(200, res.hits));
+ });
+ } catch (error) {
+ reject(new SearchResultDto(700, error));
+ }
+ });
+ }
+
+ /**
+ * Finds relevant documents by context using the given query string
+ * @param query_str
+ * @returns Elasticsearch hits or an error object
+ */
+ async findByContext(query_str: string): Promise<SearchResultDto> {
+ let es_query = {
+ query: {
+ query_string: {
+ query: query_str,
+ default_field: "content"
+ }
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ (this.httpService.get<EsResponseDto>('http://localhost:'+ this.ES_PORT + '/_search', {
+ data: es_query,
+ headers: {'Content-Type': 'application/json'},
+ }))
+ .pipe(take(1), map(axiosRes => axiosRes.data))
+ .subscribe((res: any) => {
+ if (res.timed_out) {
+ reject(new SearchResultDto(504, {status: 504, message: 'Timed Out'}));
+ }
+
+ if (!res.hits.hits.length) {
+ reject(new SearchResultDto(404, {status: 404, message: 'Not Found'}));
+ }
+
+ resolve(new SearchResultDto(200, res.hits));
+ });
+ } catch (error) {
+ reject(new SearchResultDto(700, error));
+ }
+ });
+ }
+}
+ +
+ src/core/domain/interfaces/http-response.interface.ts
+
+
+
Basic HTTP response interface
+ + + + +
+ Properties+ |
+
+
|
+
+ + data + + + + + | +
+ data:
+ |
+
+ Type : any
+
+ |
+
+ Represents the actual data which is returned by the API. In case of empty response we will have it empty also. + |
+
+ + description + + + + + | +
+ description:
+ |
+
+ Type : string
+
+ |
+
+ Represents a full description about the response (https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) + |
+
+ + message + + + + + | +
+ message:
+ |
+
+ Type : string
+
+ |
+
+ Represents a short message about the response status. + |
+
+ + status + + + + + | +
+ status:
+ |
+
+ Type : number
+
+ |
+
+ Represents the status code of the http response(https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). + |
+
+ + type + + + + + | +
+ type:
+ |
+
+ Type : string
+
+ |
+
+ Represents the type of the response + |
+
export interface HttpResponse {
+ /**
+ * Represents the type of the response
+ */
+ type: string;
+ /**
+ * Represents the status code of the http response(https://en.wikipedia.org/wiki/List_of_HTTP_status_codes).
+ */
+ status: number;
+ /**
+ * Represents a short message about the response status.
+ */
+ message: string;
+ /**
+ * Represents a full description about the response (https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
+ */
+ description: string;
+ /**
+ * Represents the actual data which is returned by the API. In case of empty response we will have it empty also.
+ */
+ data: any;
+}
+
+ +
+ src/core/domain/interfaces/page-meta.interface.ts
+
+
+
Structure of page metadata
+ + + + +
+ Properties+ |
+
+
|
+
+ + hasNext + + + + + | +
+ hasNext:
+ |
+
+ Type : boolean
+
+ |
+
+ Flag that indicates presence of the next page + |
+
+ + hasPrev + + + + + | +
+ hasPrev:
+ |
+
+ Type : boolean
+
+ |
+
+ Flag that indicates presence of the previous page + |
+
+ + order + + + + + | +
+ order:
+ |
+
+ Type : Order
+
+ |
+
+ Order of the elements on the page + |
+
+ + pagenum + + + + + | +
+ pagenum:
+ |
+
+ Type : number
+
+ |
+
+ Number of the page + |
+
+ + pagesize + + + + + | +
+ pagesize:
+ |
+
+ Type : number
+
+ |
+
+ Number of elements on the page + |
+
import { Order } from "../enums/page-order.enum";
+
+/**
+ * Structure of page metadata
+ */
+export interface PageMeta {
+ /**
+ * Number of the page
+ */
+ pagenum: number;
+
+ /**
+ * Order of the elements on the page
+ */
+ order: Order;
+
+ /**
+ * Flag that indicates presence of the next page
+ */
+ hasNext: boolean;
+
+ /**
+ * Flag that indicates presence of the previous page
+ */
+ hasPrev: boolean;
+
+ /**
+ * Number of elements on the page
+ */
+ pagesize: number;
+}
+ +
+ src/core/pipes/validation.pipe.ts
+
+
+
env variables validation pipeline
+ + + ++
+ ValidatorOptions
+
+ Properties+ |
+
+
|
+
+ + disableErrorMessages + + + + + | +
+ disableErrorMessages:
+ |
+
+ Type : boolean
+
+ |
+
+ Optional + | +
+ If error messages should be disabled + |
+
+ + exceptionFactory + + + + + | +
+ exceptionFactory:
+ |
+
+ Type : function
+
+ |
+
+ Optional + | +
+ Exception factory + |
+
+ + transform + + + + + | +
+ transform:
+ |
+
+ Type : boolean
+
+ |
+
+ Optional + | +
+ If it should be transformed + |
+
import { ValidationError, ValidatorOptions } from 'class-validator';
+/**
+ * env variables validation pipeline
+ */
+export interface ValidationPipeOptions extends ValidatorOptions {
+ /**
+ * If it should be transformed
+ */
+ transform?: boolean;
+ /**
+ * If error messages should be disabled
+ */
+ disableErrorMessages?: boolean;
+ /**
+ * Exception factory
+ */
+ exceptionFactory?: (errors: ValidationError[]) => any;
+}
+
+ +
+ src/infrastructure/config/env.objects.ts
+
+
+
VirtualBank options
+ + + + +
+ Properties+ |
+
+
|
+
+ + deposit_fee_per_minute + + + + + | +
+ deposit_fee_per_minute:
+ |
+
+ Type : number
+
+ |
+
+ Represents the fee for each minute more if customer keeps the money in our bank + |
+
+ + transaction_commission + + + + + | +
+ transaction_commission:
+ |
+
+ Type : number
+
+ |
+
+ Represents the commision amount defined for each money transaction + |
+
+ + widraw_commission + + + + + | +
+ widraw_commission:
+ |
+
+ Type : number
+
+ |
+
+ Represents the ammount of commission for each widrawal + |
+
import { expandEnvVariables } from '../../core/helpers/env.helper'
+expandEnvVariables();
+
+/**
+ * options enum
+ */
+export enum EnvObjects {
+ TRANSACTION_COMMISSION = 'VirtualBankOptions',
+ WIDRAW_COMMISSION = 'VirtualBankOptions',
+ DEPOSIT_FEE_PER_MINUTE = 'VirtualBankOptions',
+}
+//===================================================================================================
+/**
+ * VirtualBank options
+ */
+export interface VirtualBankOptions {
+ /**
+ * Represents the commision amount defined for each money transaction
+ */
+ transaction_commission: number;
+ /**
+ * Represents the ammount of commission for each widrawal
+ */
+ widraw_commission: number;
+
+ /**
+ * Represents the fee for each minute more if customer keeps the money in our bank
+ */
+ deposit_fee_per_minute: number;
+}
+
+/**
+ * configuration function
+ * @returns configuration taken from env
+ */
+export const configuration = (): any => ({
+ VirtualBankOptions: {
+ transaction_commission: process.env.TRANSACTION_COMMISSION,
+ widraw_commission: process.env.WIDRAW_COMMISSION,
+ deposit_fee_per_minute: process.env.DEPOSIT_FEE_PER_MINUTE,
+ },
+});
+
+