Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

app-nodejs-codechallenge - Josmel Yupanqui #438

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions anti-fraud/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
dist
9 changes: 9 additions & 0 deletions anti-fraud/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*.{js,ts,json}]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
9 changes: 9 additions & 0 deletions anti-fraud/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
APP_HOST = '0.0.0.0'
PORT = 3000
TOPIC_KAFKA_RECIVE_STATUS_TRANSACTION = 'transaction-status'
KAFKA_GROUP_ID = 'transaction-validate'
KAFKA_CLIENT_ID = 'my-transaction-app'
KAFKA_BROKERS = 'kafka:29092' //local localhost:9092'
TOPIC_KAFKA_SEND_TRANSACTION = 'transaction-created'
KAFKA_SERVICE = 'KAFKA_SERVICE'
KAFKA_TRANSACTION_ID = 'KAFKA_TRANSACTION_ID'
32 changes: 32 additions & 0 deletions anti-fraud/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'prettier/prettier': [
'warn',
{
singleQuote: true,
trailingComma: 'all',
arrowParens: 'always',
tabWidth: 2,
printWidth: 100,
},
],
},
};
36 changes: 36 additions & 0 deletions anti-fraud/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# compiled output
/dist
/node_modules

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.env
7 changes: 7 additions & 0 deletions anti-fraud/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"tabWidth": 2,
"printWidth": 100
}
9 changes: 9 additions & 0 deletions anti-fraud/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:16-alpine

# Bundle APP files
WORKDIR /usr/src/app
COPY . .

RUN yarn install
COPY . .
CMD ["npm", "run","start:dev"]
58 changes: 58 additions & 0 deletions anti-fraud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## Description Microservice anti-fraud

## Anti-Fraud Microservice

Este microservicio estΓ‘ diseΓ±ado para detectar y manejar transacciones fraudulentas en tiempo real. Se basa en el framework [Nest](https://github.com/nestjs/nest) utilizando Kafka para la comunicaciΓ³n entre microservicios y MongoDB para persistir las transacciones.

## DescripciΓ³n

El Microservicio Anti-Fraude consume transacciones desde un tΓ³pico de Kafka y valida el estado de cada transacciΓ³n basΓ‘ndose en reglas predefinidas. Si la transacciΓ³n supera ciertos lΓ­mites, se marca como fraudulenta, y se publica el resultado en otro tΓ³pico de Kafka para su procesamiento.

## Installation

```bash
$ cp .env.example .env
```

```bash
$ npm install
```

## Running the app

```bash
# development
$ npm run start

# watch mode
$ npm run start:dev

# production mode
$ npm run start:prod
```

## Test

```bash
# unit tests
$ npm run test

# e2e tests
$ npm run test:e2e

# test coverage
$ npm run test:cov
```

## Endpoints y API

Este microservicio no expone directamente endpoints HTTP, ya que se comunica a travΓ©s de Kafka con otros microservicios. A continuaciΓ³n se detallan los principales tΓ³picos utilizados:

- **TOPIC_KAFKA_SEND_TRANSACTION**: TΓ³pico para recibir transacciones a validar.
- **TOPIC_KAFKA_RECIVE_STATUS_TRANSACTION**: TΓ³pico para enviar el resultado del proceso de validaciΓ³n.

## TecnologΓ­as utilizadas

- **NestJS**: Framework para construir aplicaciones Node.js escalables.
- **Kafka**: Plataforma de transmisiΓ³n distribuida para manejar los mensajes entre microservicios.
- **MongoDB**: Base de datos NoSQL para almacenar las transacciones.
4 changes: 4 additions & 0 deletions anti-fraud/nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
87 changes: 87 additions & 0 deletions anti-fraud/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"name": "anti-fraud",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "npm run prebuild && nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/config": "^2.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/cqrs": "^8.0.3",
"@nestjs/event-emitter": "^1.1.0",
"@nestjs/microservices": "^8.4.4",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/swagger": "^5.2.0",
"@nestjs/typeorm": "^8.0.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"kafkajs": "^1.16.0",
"mongodb": "^4.5.0",
"pg": "^8.7.3",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"swagger-ui-express": "^4.5.0",
"typeorm": "^0.2.45"
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/express": "^4.17.13",
"@types/jest": "27.4.1",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.2.5",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"_moduleAliases": {
"@constants": "dist/constants"
}
}
28 changes: 28 additions & 0 deletions anti-fraud/src/anti-fraud.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Module, Provider } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { TransactionOpenedHandler } from './application/event/transaction-opened.handler';
import { InjectionUseCase } from './application/injection.use-case';
import { ValidateTransactionHandler } from './application/validate-transaction-use-case/validate-transaction.handler';
import { TransactionFactory } from './domain/transaction.factory';
import { TransactionConsumer } from './infrastructure/consumers/transaction.consumer';
import { IntegrationEventPublisherImplement } from './infrastructure/producers/transaction-event.producer';
import { LoggerService } from './infrastructure/logging/logger.service';
import { ClientsModule } from '@nestjs/microservices';
import { kafkaConfig } from './infrastructure/config/kafka.config';

const infrastructure: Provider[] = [
{
provide: InjectionUseCase.INTEGRATION_EVENT_PUBLISHER,
useClass: IntegrationEventPublisherImplement,
},
LoggerService,
];
const application = [TransactionOpenedHandler, ValidateTransactionHandler];
const domain = [TransactionFactory];
@Module({
imports: [CqrsModule, ClientsModule.register([kafkaConfig])],
controllers: [TransactionConsumer],
providers: [...infrastructure, ...application, ...domain],
exports: [ClientsModule],
})
export class AntiFraudModule {}
17 changes: 17 additions & 0 deletions anti-fraud/src/api/dto/read-transaction.param.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface bodyData {
id: string;
accountExternalIdDebit: string;
accountExternalIdCredit: string;
tranferTypeId: number;
value: number;
transactionStatus: number;
}

export class AntiFraudDto {
readonly topic: string;
readonly partition: number;
readonly value: {
subject: string;
data: bodyData;
};
}
14 changes: 14 additions & 0 deletions anti-fraud/src/application/event/integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TransactionEssentialProperties } from '../../domain/transaction.aggregate';

export class IntegrationEvent {
readonly subject: string;
readonly data: TransactionEssentialProperties;
}

export interface IntegrationEventPublisher {
publish: (event: IntegrationEvent) => Promise<void>;
}

export enum IntegrationEventSubject {
OPENED = 'transaction-validate.opened',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TransactionOpenedHandler } from './transaction-opened.handler';
import { TransactionOpenedEvent } from '../../domain/transaction-opened.event';
import { IntegrationEventPublisher, IntegrationEventSubject } from './integration';
import { InjectionUseCase } from '../injection.use-case';

describe('TransactionOpenedHandler', () => {
let handler: TransactionOpenedHandler;
let publisher: IntegrationEventPublisher;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TransactionOpenedHandler,
{
provide: InjectionUseCase.INTEGRATION_EVENT_PUBLISHER,
useValue: {
publish: jest.fn(),
},
},
],
}).compile();

handler = module.get<TransactionOpenedHandler>(TransactionOpenedHandler);
publisher = module.get<IntegrationEventPublisher>(InjectionUseCase.INTEGRATION_EVENT_PUBLISHER);
});

it('should handle TransactionOpenedEvent and publish the event', async () => {
const mockEvent: TransactionOpenedEvent = {
id: 'some-id',
transactionStatus: 1,
value: 1000,
};

await handler.handle(mockEvent);

expect(publisher.publish).toHaveBeenCalledWith({
subject: IntegrationEventSubject.OPENED,
data: mockEvent,
});
});
});
21 changes: 21 additions & 0 deletions anti-fraud/src/application/event/transaction-opened.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Inject } from '@nestjs/common';
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { TransactionOpenedEvent } from '../../domain/transaction-opened.event';
import { InjectionUseCase } from '../injection.use-case';

import { IntegrationEventPublisher, IntegrationEventSubject } from './integration';

@EventsHandler(TransactionOpenedEvent)
export class TransactionOpenedHandler implements IEventHandler<TransactionOpenedEvent> {
constructor(
@Inject(InjectionUseCase.INTEGRATION_EVENT_PUBLISHER)
private readonly publisher: IntegrationEventPublisher,
) {}

async handle(event: TransactionOpenedEvent): Promise<void> {
await this.publisher.publish({
subject: IntegrationEventSubject.OPENED,
data: event,
});
}
}
Loading