Skip to content
Closed
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ dist

# TernJS port file
.tern-port

package-lock.json
172 changes: 107 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,124 @@
# Yape Code Challenge :rocket:
# PROYECTO APP-NODEJS-CODECHALLENGE

Our code challenge will let you marvel us with your Jedi coding skills :smile:.
El proyecto ha sido desarrollado con NodeJS, Koa, Graphql y Kafka. Utilizando el lenguaje de programación Typescript.

Don't forget that the proper way to submit your work is to fork the repo and create a PR :wink: ... have fun !!
1. anti-fraud: Valida si el valor de la transación si es aprobado o rechazado.
2. transaction: Obtiene la transacción, registra la transacción y actualiza el estado de la transacción.

- [Problem](#problem)
- [Tech Stack](#tech_stack)
- [Send us your challenge](#send_us_your_challenge)
Seguir los siguientes pasos para usar el proyecto.

# Problem
### Descargar e instalar versión de nodejs >= 20.x.x
- ```https://nodejs.org/en/download```

Every time a financial transaction is created it must be validated by our anti-fraud microservice and then the same service sends a message back to update the transaction status.
For now, we have only three transaction statuses:
### Clonar el repositorio app-nodejs-codechallenge
- Ejecutar el comando: ```git clone https://github.com/amamanipu/app-nodejs-codechallenge.git```

<ol>
<li>pending</li>
<li>approved</li>
<li>rejected</li>
</ol>
### Descargar dependencias y ejecutar microservicio anti-fraud en local
- Ubicarnos en microservicio: ```cd anti-fraud```
- Descargar dependencias: ```npm install```
- Ejecutar microservicio: `npm run dev`

Every transaction with a value greater than 1000 should be rejected.
### Descargar dependencias y ejecutar microservicio transaction en local
- Ubicarnos en microservicio: ```cd transaction```
- Descargar dependencias: ```npm install```
- Ejecutar microservicio: ```npm run dev```

```mermaid
flowchart LR
Transaction -- Save Transaction with pending Status --> transactionDatabase[(Database)]
Transaction --Send transaction Created event--> Anti-Fraud
Anti-Fraud -- Send transaction Status Approved event--> Transaction
Anti-Fraud -- Send transaction Status Rejected event--> Transaction
Transaction -- Update transaction Status event--> transactionDatabase[(Database)]
```

# Tech Stack

<ol>
<li>Node. You can use any framework you want (i.e. Nestjs with an ORM like TypeOrm or Prisma) </li>
<li>Any database</li>
<li>Kafka</li>
</ol>
### Ejecutar pruebas unitarias en cada microservicio
- Ejecutar el comando: ```npm run test```

We do provide a `Dockerfile` to help you get started with a dev environment.
### Estructura del Proyecto
La carpeta contiene:

You must have two resources:
- `anti-fraud` - Contiene codigo de validar de transacción.
- `transaction` - Contiene codigo de obtener, registrar y actualizar transacción.

1. Resource to create a transaction that must containt:
```
.
├── anti-fraud
│ ├── src
│ │ ├── common
│ │ │ └── constants.ts
│ │ ├── infrastructure
│ │ │ └── kafka
│ │ │ ├── consumers
│ │ │ │ └── transaction.consumer.ts
│ │ │ └── producers
│ │ │ └── transactionStatus.producer.ts
│ │ └── main.ts
│ ├── package.json
│ └── tsconfig.json
├── transaction
│ ├── src
│ │ ├── application
│ │ │ └── services
│ │ │ └── transaction.service.ts
│ │ ├── common
│ │ │ └── constants.ts
│ │ ├── domain
│ │ │ └── repositories
│ │ │ └── transaction.repository.ts
│ │ ├── infrastructure
│ │ │ ├── graphql
│ │ │ │ ├── transaction
│ │ │ │ │ ├── transaction.resolver.ts
│ │ │ │ │ └── transaction.schema.ts
│ │ │ │ └── schema.ts
│ │ │ ├── kafka
│ │ │ │ ├── consumers
│ │ │ │ │ └── transactionStatus.consumer.ts
│ │ │ │ └── producers
│ │ │ │ └── transaction.producer.ts
│ │ │ └── orm
│ │ │ └── sequelize
│ │ │ └── postgresql
│ │ │ ├── models
│ │ │ │ ├── transaction.model.ts
│ │ │ │ ├── transactionStatus.model.ts
│ │ │ │ └── transactionType.model.ts
│ │ │ ├── repositories
│ │ │ │ └── transaction.repository.ts
│ │ │ └── connection.ts
│ │ ├── container.ts
│ │ └── main.ts
│ ├── test
│ ├── package.json
│ └── tsconfig.json
└── README.md
```

```json
{
"accountExternalIdDebit": "Guid",
"accountExternalIdCredit": "Guid",
"tranferTypeId": 1,
"value": 120
### Uso de Graphql

- URI: ```http://localhost:3000/graphql```

```graphql
query {
transaction(transactionExternalId: "2a6d5b76-cf85-406a-9c62-877cfe3e36fc") {
transactionExternalId,
transactionType {
name
},
transactionStatus {
name
},
value,
createdAt
}
}
```

2. Resource to retrieve a transaction

```json
{
"transactionExternalId": "Guid",
"transactionType": {
"name": ""
},
"transactionStatus": {
"name": ""
},
"value": 120,
"createdAt": "Date"
```graphql
mutation {
createTransaction(
TransactionInput: {
accountExternalIdDebit: "d6eb621f-6dd0-4cdc-93f5-07f51b249b51",
accountExternalIdCredit: "d6eb621f-6dd0-4cdc-93f5-07f51b249b51",
tranferTypeId: 1,
value: 750
}
) {
transactionExternalId
}
}
```

## Optional

You can use any approach to store transaction data but you should consider that we may deal with high volume scenarios where we have a huge amount of writes and reads for the same data at the same time. How would you tackle this requirement?

You can use Graphql;

# Send us your challenge

When you finish your challenge, after forking a repository, you **must** open a pull request to our repository. There are no limitations to the implementation, you can follow the programming paradigm, modularization, and style that you feel is the most appropriate solution.

If you have any questions, please let us know.
20 changes: 20 additions & 0 deletions anti-fraud/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "anti-fraud",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "npx ts-node-dev --inspect --respawn src/main.ts"
},
"keywords": [],
"author": "Angel Mamani<amamani.9508@gmail.com>",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.12.13",
"typescript": "^5.4.5"
},
"dependencies": {
"kafkajs": "^2.2.4"
}
}
5 changes: 5 additions & 0 deletions anti-fraud/src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum STATUS {
Pending = 1,
Approved = 2,
Rejected = 3,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Kafka } from "kafkajs";
import { STATUS } from "../../../common/constants";
import { TransactionStatusProducer } from "../producers/transactionStatus.producer";

export const TransactionConsumer = (config: any) => {
const kafka = new Kafka({
brokers: config.brokers
});

const processData = async (message: any) => {
try {
if (message === null) throw new Error('Mensaje nulo');
let event = JSON.parse(message);

if (event.value > 0 && event.value <= 1000) {
event.statusId = STATUS.Approved;
} else {
event.statusId = STATUS.Rejected;
}

await TransactionStatusProducer(config).send(event);
} catch (error: any) {
console.error(error);
}
}

const subscribe = async () => {
const consumer = kafka.consumer({
groupId: config.topicTransaction,
});

await consumer.connect();
await consumer.subscribe({
topic: config.topicTransaction,
fromBeginning: true,
});

await consumer.
run({
eachMessage: async ({ message }) => {
const data = message.value?.toString() ?? null;
await processData(data);
}
})
.catch(console.error);

console.info('Se suscribio correctamente al topico: ' + config.topicTransaction);
}

return { subscribe };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Kafka } from 'kafkajs';

export const TransactionStatusProducer = (config: any) => {
const kafka = new Kafka({
brokers: config.brokers
})

const producer = kafka.producer();
const send = async(message: any) => {
await producer.connect();
await producer.send({
topic: config.topicTransactionStatus,
messages: [{value: JSON.stringify(message)}],
}).then(() => {
console.info(`TransactionStatus :: Evento publicado: ${JSON.stringify(message)}`);
}).catch(console.error);
await producer.disconnect();
};

return { send };
}
18 changes: 18 additions & 0 deletions anti-fraud/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TransactionConsumer } from "./infrastructure/kafka/consumers/transaction.consumer";

async function startServer() {

// Configurar desde secrets manager
const config = {
brokers: ['localhost:9092'],
topicTransaction: 'topic_transaction',
topicTransactionStatus: 'topic_transaction_status'
};

// Consumidor de Kafka
await TransactionConsumer(config)
.subscribe()
.catch(console.error);
}

startServer();
15 changes: 15 additions & 0 deletions anti-fraud/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
"outDir": "./dist",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
45 changes: 45 additions & 0 deletions transaction/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "transaction",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "npx mocha -r ts-node/register 'test/**/*.test.ts'",
"dev": "npx ts-node-dev --inspect --respawn src/main.ts"
},
"keywords": [],
"author": "Angel Mamani<amamani.9508@gmail.com>",
"license": "MIT",
"devDependencies": {
"@types/chai": "^4.2.22",
"@types/koa": "^3.0.0",
"@types/koa-bodyparser": "^4.3.12",
"@types/koa-router": "^7.4.8",
"@types/mocha": "^9.1.1",
"@types/node": "^20.12.13",
"@types/sinon": "^17.0.3",
"chai": "^4.3.6",
"mocha": "^10.1.0",
"sinon": "^18.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
"dependencies": {
"@apollo/server": "^4.12.2",
"@as-integrations/koa": "^1.1.1",
"@types/pg": "^8.11.6",
"@types/sequelize": "^4.28.20",
"graphql": "^16.11.0",
"kafkajs": "^2.2.4",
"koa": "^2.15.3",
"koa-bodyparser": "^4.4.1",
"koa-router": "^12.0.1",
"pg": "^8.11.5",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.3",
"type-graphql": "^2.0.0-rc.1",
"typedi": "^0.10.0",
"uuidv4": "^6.2.13"
}
}
Loading