Skip to content

Commit f28c704

Browse files
committed
feat: Se actualiza endpoints a dos
1 parent a9bb083 commit f28c704

36 files changed

Lines changed: 544 additions & 207 deletions

README.md

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,126 @@
1-
## 🚀 Instrucciones para levantar el entorno
1+
# app-nodejs-codechallenge
22

3-
### 1. Crear archivo .env en la raiz
3+
Este repositorio es un microservicio en NestJS para manejar transacciones financieras. Usa PostgreSQL para persistencia y Kafka para la validación asíncrona (anti-fraud).
44

5+
Este README describe cómo levantar el entorno, cómo usar los servicios (endpoints) y cómo comprobar el flujo de mensajes con Kafka.
6+
7+
## Resumen técnico
8+
- Framework: NestJS
9+
- Persistencia: PostgreSQL (TypeORM)
10+
- Mensajería: Kafka
11+
- Arquitectura: monolito modular con flujo event-driven para validación anti-fraud
12+
13+
## Requisitos
14+
- Docker & Docker Compose
15+
- Node 18+ y npm o yarn
16+
17+
## Configuración (.env)
18+
Crear un archivo `.env` en la raíz con las variables de Postgres:
19+
20+
```
521
POSTGRES_HOST=localhost
622
POSTGRES_PORT=5432
723
POSTGRES_USER=postgres
824
POSTGRES_PASSWORD=postgres
925
POSTGRES_DB=postgres
26+
```
1027

11-
### 2. Instalar las dependencias
28+
Nota: la app por defecto usa `localhost:9092` como broker Kafka. Si ejecutas la app dentro de Docker, usa `kafka:29092` como broker.
1229

13-
npm install
30+
## Levantar el entorno
1431

15-
# o
32+
1. Instala dependencias:
1633

34+
```bash
35+
npm install
36+
# o
1737
yarn install
38+
```
1839

19-
### 3. Levantar los servicios de base de datos, zookeeper y kafka
40+
2. Levanta infra con Docker Compose (Postgres, Zookeeper, Kafka):
2041

21-
docker-compose up -d
42+
```bash
43+
docker compose up -d
44+
```
2245

23-
### 4. Correr la aplicacion
46+
3. Ejecuta la aplicación en modo desarrollo:
2447

48+
```bash
2549
npm run local
26-
2750
# o
28-
2951
yarn local
52+
```
53+
54+
## Cómo usar los servicios (endpoints)
55+
56+
Base URL: `http://localhost:3000`
57+
58+
1) Crear transacción — POST `/transaction`
59+
60+
- Descripción: crea una transacción y publica el evento para validación anti-fraud.
61+
- Body (JSON):
62+
63+
```json
64+
{
65+
"accountExternalIdDebit": "5429d629-c239-45fa-8235-1a386258c536",
66+
"accountExternalIdCredit": "d6cd54da-8ce3-4f79-abda-bd5be9b19e68",
67+
"tranferTypeId": 1,
68+
"value": 120
69+
}
70+
```
71+
72+
Ejemplos `curl`:
73+
74+
```bash
75+
curl -i -X POST http://localhost:3000/transaction \
76+
-H "Content-Type: application/json" \
77+
-d '{
78+
"accountExternalIdDebit": "5429d629-c239-45fa-8235-1a386258c536",
79+
"accountExternalIdCredit": "d6cd54da-8ce3-4f79-abda-bd5be9b19e68",
80+
"tranferTypeId": 1,
81+
"value": 120
82+
}'
83+
```
84+
85+
2) Listar transacciones — GET `/transaction`
86+
87+
- Descripción: devuelve todas las transacciones guardadas (útil para debug local).
88+
89+
```bash
90+
curl http://localhost:3000/transaction
91+
```
92+
93+
## Flujo Kafka (anti-fraud)
94+
95+
- Al crear una transacción se publica un evento `transaction.created` con los datos básicos.
96+
- Un consumidor (anti-fraud) valida la transacción y emite `transaction.validated` con `{ id, status }`.
97+
- La app escucha `transaction.validated` y actualiza el `status` en la base de datos.
98+
99+
Regla implementada en el anti-fraud (ejemplo): `if value > 1000 => rejected`; else `approved`.
100+
101+
3. Consultar la última transacción:
102+
103+
```sql
104+
select * from transaction where "accountExternalIdDebit" = {{ID}};
105+
```
106+
107+
## DDL (esquema) para probar localmente
30108

31-
### 5. Probar los servicios usando los siguientes curl
109+
```sql
32110

33-
curl --location 'localhost:3000/transaction' \
34-
--header 'Content-Type: application/json' \
35-
--data '{
36-
"accountExternalIdDebit": "5429d629-c239-45fa-8235-1a386258c536",
37-
"accountExternalIdCredit": "d6cd54da-8ce3-4f79-abda-bd5be9b19e68",
38-
"tranferTypeId": 1,
39-
"value": 120
40-
}'
111+
CREATE TABLE public.transaction
112+
(
113+
"accountExternalIdDebit" varchar NOT NULL,
114+
"accountExternalIdCredit" varchar NOT NULL,
115+
"tranferTypeId" integer NOT NULL,
116+
value numeric NOT NULL,
117+
id uuid DEFAULT uuid_generate_v4() NOT NULL
118+
CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9"
119+
PRIMARY KEY,
120+
"createdAt" timestamp DEFAULT now() NOT NULL,
121+
"updatedAt" timestamp DEFAULT now() NOT NULL,
122+
status integer NOT NULL
123+
);
124+
```
41125

42-
curl --location 'localhost:3000/transaction/5429d629-c239-45fa-8235-1a386258c536'
126+
Si usas TypeORM, la entidad del proyecto crea automáticamente la tabla al iniciar (si `synchronize: true`). Usa este DDL solo si quieres preparar la tabla manualmente o probar fuera del ciclo de arranque de la app.
File renamed without changes.
File renamed without changes.
File renamed without changes.

package.json renamed to anti-fraud/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "app-nodejs-codechallenge",
2+
"name": "anti-fraud",
33
"version": "0.0.1",
44
"description": "",
55
"author": "",

anti-fraud/src/app.controller.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Controller, Logger } from '@nestjs/common';
2+
import { MessagePattern, Payload } from '@nestjs/microservices';
3+
import { AppService } from './app.service';
4+
import { TransactionCreatedEvent } from './models/transaction-created.event';
5+
6+
@Controller()
7+
export class AppController {
8+
constructor(private readonly appService: AppService) {}
9+
10+
@MessagePattern('transaction.created')
11+
async approvedTransaction(
12+
@Payload() payload: TransactionCreatedEvent,
13+
): Promise<void> {
14+
Logger.debug(payload);
15+
await this.appService.evaluateTransaction(payload);
16+
}
17+
}

anti-fraud/src/app.module.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Module } from '@nestjs/common';
2+
import { ConfigModule } from '@nestjs/config';
3+
import { ClientsModule, Transport } from '@nestjs/microservices';
4+
import { AppController } from './app.controller';
5+
import { AppService } from './app.service';
6+
7+
@Module({
8+
imports: [
9+
ConfigModule.forRoot(),
10+
ClientsModule.register([
11+
{
12+
name: 'KAFKA',
13+
transport: Transport.KAFKA,
14+
options: {
15+
client: {
16+
clientId: 'anti-fraud',
17+
brokers: [`${process.env.KAFKA_HOST}:9092`],
18+
},
19+
consumer: {
20+
groupId: 'anti-fraud-consumer',
21+
},
22+
},
23+
},
24+
]),
25+
],
26+
controllers: [AppController],
27+
providers: [AppService]
28+
})
29+
export class AppModule { }

anti-fraud/src/app.service.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Inject, Injectable } from '@nestjs/common';
2+
import { ClientKafka } from '@nestjs/microservices';
3+
import { TransactionCreatedEvent } from './models/transaction-created.event';
4+
import { TransactionRejectedEvent } from './models/transaction-rejected.event';
5+
import { TransactionApprovedEvent } from './models/transaction-approved.event.ts';
6+
7+
@Injectable()
8+
export class AppService {
9+
constructor(
10+
@Inject('KAFKA')
11+
private readonly eventBus: ClientKafka,
12+
) {}
13+
14+
async evaluateTransaction(payload: TransactionCreatedEvent): Promise<void> {
15+
const evaluatedEvent =
16+
payload.value > 1000
17+
? TransactionRejectedEvent
18+
: TransactionApprovedEvent;
19+
this.eventBus.emit(
20+
evaluatedEvent.getName(),
21+
evaluatedEvent.toEvent(payload),
22+
);
23+
}
24+
}

anti-fraud/src/main.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { NestFactory } from '@nestjs/core';
2+
import { AppModule } from './app.module';
3+
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
4+
5+
async function bootstrap() {
6+
const app = await NestFactory.createMicroservice(AppModule, {
7+
transport: Transport.KAFKA,
8+
options: {
9+
client: {
10+
brokers: [`${process.env.KAFKA_HOST}:9092`],
11+
},
12+
consumer: {
13+
groupId: 'transaction-consumer',
14+
},
15+
},
16+
} as MicroserviceOptions);
17+
await app.listen();
18+
}
19+
bootstrap();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TransactionCreatedEvent } from './transaction-created.event';
2+
3+
export class TransactionApprovedEvent {
4+
static getName(): string {
5+
return 'transaction.approved';
6+
}
7+
8+
static toEvent(transaction: TransactionCreatedEvent): string {
9+
return JSON.stringify({
10+
transactionExternalId: transaction.transactionExternalId,
11+
transactionType: transaction.transactionType,
12+
transactionStatus: transaction.transactionStatus,
13+
value: transaction.value,
14+
createdAt: transaction.createdAt,
15+
});
16+
}
17+
}

0 commit comments

Comments
 (0)