From e06ec2fd7f0f634e1c5b57411f28d9a1b477efa4 Mon Sep 17 00:00:00 2001 From: Eisten Flores Sanchez Date: Mon, 15 Dec 2025 00:25:36 -0500 Subject: [PATCH] feat: add transaction service --- README.md | 124 ++++++++++-------- apps/anti-fraud/.gitignore | 56 ++++++++ apps/anti-fraud/.prettierrc | 4 + apps/anti-fraud/Dockerfile | 23 ++++ apps/anti-fraud/README.md | 98 ++++++++++++++ apps/anti-fraud/eslint.config.mjs | 35 +++++ apps/anti-fraud/nest-cli.json | 8 ++ apps/anti-fraud/package.json | 74 +++++++++++ apps/anti-fraud/src/app.controller.spec.ts | 22 ++++ apps/anti-fraud/src/app.controller.ts | 13 ++ apps/anti-fraud/src/app.module.ts | 28 ++++ apps/anti-fraud/src/app.service.ts | 25 ++++ apps/anti-fraud/src/main.ts | 25 ++++ apps/anti-fraud/test/app.e2e-spec.ts | 25 ++++ apps/anti-fraud/test/jest-e2e.json | 9 ++ apps/anti-fraud/tsconfig.build.json | 4 + apps/anti-fraud/tsconfig.json | 25 ++++ apps/transaction/.gitignore | 58 ++++++++ apps/transaction/.prettierrc | 4 + apps/transaction/Dockerfile | 20 +++ apps/transaction/README.md | 98 ++++++++++++++ apps/transaction/eslint.config.mjs | 35 +++++ apps/transaction/nest-cli.json | 8 ++ apps/transaction/package.json | 76 +++++++++++ apps/transaction/prisma/schema.prisma | 26 ++++ apps/transaction/src/app.controller.spec.ts | 22 ++++ apps/transaction/src/app.controller.ts | 40 ++++++ apps/transaction/src/app.module.ts | 33 +++++ apps/transaction/src/app.service.ts | 62 +++++++++ .../src/dtos/create-transaction.dto.ts | 6 + apps/transaction/src/main.ts | 24 ++++ apps/transaction/src/prisma.service.ts | 13 ++ apps/transaction/test/app.e2e-spec.ts | 25 ++++ apps/transaction/test/jest-e2e.json | 9 ++ apps/transaction/tsconfig.build.json | 4 + apps/transaction/tsconfig.json | 25 ++++ docker-compose.yml | 82 +++++++++--- package.json | 10 ++ 38 files changed, 1205 insertions(+), 73 deletions(-) create mode 100644 apps/anti-fraud/.gitignore create mode 100644 apps/anti-fraud/.prettierrc create mode 100644 apps/anti-fraud/Dockerfile create mode 100644 apps/anti-fraud/README.md create mode 100644 apps/anti-fraud/eslint.config.mjs create mode 100644 apps/anti-fraud/nest-cli.json create mode 100644 apps/anti-fraud/package.json create mode 100644 apps/anti-fraud/src/app.controller.spec.ts create mode 100644 apps/anti-fraud/src/app.controller.ts create mode 100644 apps/anti-fraud/src/app.module.ts create mode 100644 apps/anti-fraud/src/app.service.ts create mode 100644 apps/anti-fraud/src/main.ts create mode 100644 apps/anti-fraud/test/app.e2e-spec.ts create mode 100644 apps/anti-fraud/test/jest-e2e.json create mode 100644 apps/anti-fraud/tsconfig.build.json create mode 100644 apps/anti-fraud/tsconfig.json create mode 100644 apps/transaction/.gitignore create mode 100644 apps/transaction/.prettierrc create mode 100644 apps/transaction/Dockerfile create mode 100644 apps/transaction/README.md create mode 100644 apps/transaction/eslint.config.mjs create mode 100644 apps/transaction/nest-cli.json create mode 100644 apps/transaction/package.json create mode 100644 apps/transaction/prisma/schema.prisma create mode 100644 apps/transaction/src/app.controller.spec.ts create mode 100644 apps/transaction/src/app.controller.ts create mode 100644 apps/transaction/src/app.module.ts create mode 100644 apps/transaction/src/app.service.ts create mode 100644 apps/transaction/src/dtos/create-transaction.dto.ts create mode 100644 apps/transaction/src/main.ts create mode 100644 apps/transaction/src/prisma.service.ts create mode 100644 apps/transaction/test/app.e2e-spec.ts create mode 100644 apps/transaction/test/jest-e2e.json create mode 100644 apps/transaction/tsconfig.build.json create mode 100644 apps/transaction/tsconfig.json create mode 100644 package.json diff --git a/README.md b/README.md index b067a71026..c0b85f48e9 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,90 @@ -# Yape Code Challenge :rocket: +# Yape Code Challenge -Our code challenge will let you marvel us with your Jedi coding skills :smile:. +Solución para el reto técnico de Yape. -Don't forget that the proper way to submit your work is to fork the repo and create a PR :wink: ... have fun !! +## Descripción -- [Problem](#problem) -- [Tech Stack](#tech_stack) -- [Send us your challenge](#send_us_your_challenge) +El sistema consta de dos microservicios desacoplados que se comunican de forma asíncrona. El objetivo es procesar transacciones de alto volumen y validar reglas de negocio antifraude sin bloquear la respuesta al cliente. -# Problem +### Flujo de la Aplicación +1. **Transaction Service** recibe una solicitud HTTP para crear una transacción. +2. La transacción se guarda en **MySQL** con estado `PENDING`. +3. Se emite un evento `transaction_created` a **Kafka**. +4. **Anti-Fraud Service** consume el evento y valida el monto. +5. Si el valor es **> 1000**, se rechaza (`REJECTED`); de lo contrario, se aprueba (`APPROVED`). +6. Se emite un evento `transaction_status_update` de vuelta. +7. **Transaction Service** actualiza el estado final en la base de datos. -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: +## Tecnologías -
    -
  1. pending
  2. -
  3. approved
  4. -
  5. rejected
  6. -
+* **Node.js & NestJS**: Framework principal (Modo Híbrido: HTTP + Microservice). +* **Apache Kafka**: Message Broker para comunicación asíncrona. +* **MySQL**: Base de datos relacional. +* **Prisma ORM**: Gestión de esquemas y migraciones automáticas. +* **Docker & Docker Compose**: Orquestación de contenedores y entorno de desarrollo. -Every transaction with a value greater than 1000 should be rejected. +## Instalación y Ejecución -```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)] -``` +### Prerrequisitos +* Docker +* Git -# Tech Stack +### Pasos +1. Clona el repositorio: + ```bash + git clone + cd + ``` -
    -
  1. Node. You can use any framework you want (i.e. Nestjs with an ORM like TypeOrm or Prisma)
  2. -
  3. Any database
  4. -
  5. Kafka
  6. -
+2. Ejecuta el entorno con Docker Compose: + ```bash + docker-compose up --build + ``` -We do provide a `Dockerfile` to help you get started with a dev environment. +3. Verifica que los servicios estén corriendo: + * **API Transaction:** `http://localhost:3000` + * **Kafka UI (Opcional):** `http://localhost:8080` -You must have two resources: +## Uso de la API -1. Resource to create a transaction that must containt: +Puedes probar los endpoints usando `curl` o Postman. -```json -{ - "accountExternalIdDebit": "Guid", - "accountExternalIdCredit": "Guid", - "tranferTypeId": 1, - "value": 120 -} +### 1. Crear una Transacción + +Envia una transacción. Si el `value` es mayor a 1000, será rechazada. + +**Request:** +```bash +curl -X POST http://localhost:3000/transactions \ + -H "Content-Type: application/json" \ + -d '{ + "accountExternalIdDebit": "28608054-47b6-4522-9214-722e0322b270", + "accountExternalIdCredit": "28608054-47b6-4522-9214-722e0322b271", + "transferTypeId": 1, + "value": 120 + }' ``` -2. Resource to retrieve a transaction +### 2. Consultar una Transacción +Usa el transactionExternalId (UUID) que recibiste en la respuesta anterior para ver su estado final. + +**Request:** + +```bash +curl http://localhost:3000/transactions/{TU-UUID-AQUI} +``` +Respuesta Exitosa (Aprobada): -```json +```JSON { - "transactionExternalId": "Guid", + "transactionExternalId": "1f0bfb31-4577-4755-8253-60ea07d0273e", "transactionType": { - "name": "" + "name": "1" }, "transactionStatus": { - "name": "" + "name": "APPROVED" }, "value": 120, - "createdAt": "Date" + "createdAt": "2025-12-13T20:31:23.691Z" } -``` - -## 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. +``` \ No newline at end of file diff --git a/apps/anti-fraud/.gitignore b/apps/anti-fraud/.gitignore new file mode 100644 index 0000000000..4b56acfbeb --- /dev/null +++ b/apps/anti-fraud/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# 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 + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/apps/anti-fraud/.prettierrc b/apps/anti-fraud/.prettierrc new file mode 100644 index 0000000000..a20502b7f0 --- /dev/null +++ b/apps/anti-fraud/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/apps/anti-fraud/Dockerfile b/apps/anti-fraud/Dockerfile new file mode 100644 index 0000000000..6c53074b56 --- /dev/null +++ b/apps/anti-fraud/Dockerfile @@ -0,0 +1,23 @@ +# Usa una imagen ligera de Node +FROM node:18-alpine + +# Directorio de trabajo dentro del contenedor +WORKDIR /app + +# Copiamos archivos de dependencias primero (para aprovechar el caché de Docker) +COPY package*.json ./ + +# Instalamos dependencias +RUN npm install + +# Copiamos el resto del código +COPY . . + +# Generamos el cliente de Prisma (Si usas Prisma, descomenta esta línea) +# RUN npx prisma generate + +# Construimos la aplicación NestJS +RUN npm run build + +# Comando para iniciar la app (dist/main es el estándar de NestJS) +CMD ["node", "dist/main"] \ No newline at end of file diff --git a/apps/anti-fraud/README.md b/apps/anti-fraud/README.md new file mode 100644 index 0000000000..8f0f65f7e7 --- /dev/null +++ b/apps/anti-fraud/README.md @@ -0,0 +1,98 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Project setup + +```bash +$ npm install +``` + +## Compile and run the project + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# production mode +$ npm run start:prod +``` + +## Run tests + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` + +## Deployment + +When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. + +If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: + +```bash +$ npm install -g @nestjs/mau +$ mau deploy +``` + +With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/apps/anti-fraud/eslint.config.mjs b/apps/anti-fraud/eslint.config.mjs new file mode 100644 index 0000000000..4e9f8271c9 --- /dev/null +++ b/apps/anti-fraud/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + sourceType: 'commonjs', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + "prettier/prettier": ["error", { endOfLine: "auto" }], + }, + }, +); diff --git a/apps/anti-fraud/nest-cli.json b/apps/anti-fraud/nest-cli.json new file mode 100644 index 0000000000..f9aa683b1a --- /dev/null +++ b/apps/anti-fraud/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/apps/anti-fraud/package.json b/apps/anti-fraud/package.json new file mode 100644 index 0000000000..842c8d8039 --- /dev/null +++ b/apps/anti-fraud/package.json @@ -0,0 +1,74 @@ +{ + "name": "anti-fraud", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "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": "^11.0.1", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.1", + "@nestjs/microservices": "^11.1.9", + "@nestjs/platform-express": "^11.0.1", + "kafkajs": "^2.2.4", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.10.7", + "@types/supertest": "^6.0.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^16.0.0", + "jest": "^30.0.0", + "prettier": "^3.4.2", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/apps/anti-fraud/src/app.controller.spec.ts b/apps/anti-fraud/src/app.controller.spec.ts new file mode 100644 index 0000000000..d22f3890a3 --- /dev/null +++ b/apps/anti-fraud/src/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/apps/anti-fraud/src/app.controller.ts b/apps/anti-fraud/src/app.controller.ts new file mode 100644 index 0000000000..f2738ba5b5 --- /dev/null +++ b/apps/anti-fraud/src/app.controller.ts @@ -0,0 +1,13 @@ +import { Controller } from '@nestjs/common'; +import { EventPattern, Payload } from '@nestjs/microservices'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @EventPattern('transaction_created') + handleTransactionCreated(@Payload() data: any) { + this.appService.handleTransactionCreated(data); + } +} \ No newline at end of file diff --git a/apps/anti-fraud/src/app.module.ts b/apps/anti-fraud/src/app.module.ts new file mode 100644 index 0000000000..5842630166 --- /dev/null +++ b/apps/anti-fraud/src/app.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'KAFKA_SERVICE', + transport: Transport.KAFKA, + options: { + client: { + brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'], + }, + consumer: { + groupId: 'transaction-consumer-client', + }, + }, + }, + ]), + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/apps/anti-fraud/src/app.service.ts b/apps/anti-fraud/src/app.service.ts new file mode 100644 index 0000000000..c4b0d4f81d --- /dev/null +++ b/apps/anti-fraud/src/app.service.ts @@ -0,0 +1,25 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ClientKafka } from '@nestjs/microservices'; + +@Injectable() +export class AppService { + constructor( + @Inject('KAFKA_SERVICE') private readonly kafkaClient: ClientKafka, + ) {} + + handleTransactionCreated(data: any) { + const transaction = typeof data === 'string' ? JSON.parse(data) : data; + + console.log(`[Anti-Fraud] Analizando transacción: ${transaction.transactionExternalId} con valor ${transaction.value}`); + + const isFraud = transaction.value > 1000; + const newStatus = isFraud ? 'REJECTED' : 'APPROVED'; + + console.log(`[Anti-Fraud] Resultado: ${newStatus}. Enviando evento...`); + + this.kafkaClient.emit('transaction_status_update', JSON.stringify({ + transactionExternalId: transaction.transactionExternalId, + status: newStatus, + })); + } +} \ No newline at end of file diff --git a/apps/anti-fraud/src/main.ts b/apps/anti-fraud/src/main.ts new file mode 100644 index 0000000000..3d96928375 --- /dev/null +++ b/apps/anti-fraud/src/main.ts @@ -0,0 +1,25 @@ +import { NestFactory } from '@nestjs/core'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.createMicroservice( + AppModule, + { + transport: Transport.KAFKA, + options: { + client: { + brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'], + }, + consumer: { + groupId: 'anti-fraud-consumer', + }, + }, + }, + ); + + + await app.listen(); + console.log('Anti-Fraud Microservice is listening via Kafka...'); +} +bootstrap(); \ No newline at end of file diff --git a/apps/anti-fraud/test/app.e2e-spec.ts b/apps/anti-fraud/test/app.e2e-spec.ts new file mode 100644 index 0000000000..36852c54f0 --- /dev/null +++ b/apps/anti-fraud/test/app.e2e-spec.ts @@ -0,0 +1,25 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import request from 'supertest'; +import { App } from 'supertest/types'; +import { AppModule } from './../src/app.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/apps/anti-fraud/test/jest-e2e.json b/apps/anti-fraud/test/jest-e2e.json new file mode 100644 index 0000000000..e9d912f3e3 --- /dev/null +++ b/apps/anti-fraud/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/apps/anti-fraud/tsconfig.build.json b/apps/anti-fraud/tsconfig.build.json new file mode 100644 index 0000000000..64f86c6bd2 --- /dev/null +++ b/apps/anti-fraud/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/apps/anti-fraud/tsconfig.json b/apps/anti-fraud/tsconfig.json new file mode 100644 index 0000000000..aba29b0e7f --- /dev/null +++ b/apps/anti-fraud/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "resolvePackageJsonExports": true, + "esModuleInterop": true, + "isolatedModules": true, + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2023", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/apps/transaction/.gitignore b/apps/transaction/.gitignore new file mode 100644 index 0000000000..78cff2123d --- /dev/null +++ b/apps/transaction/.gitignore @@ -0,0 +1,58 @@ +# compiled output +/dist +/node_modules +/build + +# 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 + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +/generated/prisma diff --git a/apps/transaction/.prettierrc b/apps/transaction/.prettierrc new file mode 100644 index 0000000000..a20502b7f0 --- /dev/null +++ b/apps/transaction/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/apps/transaction/Dockerfile b/apps/transaction/Dockerfile new file mode 100644 index 0000000000..e4f454d01b --- /dev/null +++ b/apps/transaction/Dockerfile @@ -0,0 +1,20 @@ +# apps/transaction/Dockerfile +FROM node:18-alpine + +WORKDIR /app + +# Esta línea es VITAL para Alpine +RUN apk add --no-cache openssl libc6-compat + +COPY package*.json ./ + +RUN npm install + +COPY . . + +# Usamos la ruta explícita del esquema por seguridad +RUN npx prisma generate --schema=./prisma/schema.prisma + +RUN npm run build + +CMD ["npm", "run", "start:prod"] \ No newline at end of file diff --git a/apps/transaction/README.md b/apps/transaction/README.md new file mode 100644 index 0000000000..8f0f65f7e7 --- /dev/null +++ b/apps/transaction/README.md @@ -0,0 +1,98 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Project setup + +```bash +$ npm install +``` + +## Compile and run the project + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# production mode +$ npm run start:prod +``` + +## Run tests + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` + +## Deployment + +When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. + +If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: + +```bash +$ npm install -g @nestjs/mau +$ mau deploy +``` + +With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/apps/transaction/eslint.config.mjs b/apps/transaction/eslint.config.mjs new file mode 100644 index 0000000000..4e9f8271c9 --- /dev/null +++ b/apps/transaction/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + sourceType: 'commonjs', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + "prettier/prettier": ["error", { endOfLine: "auto" }], + }, + }, +); diff --git a/apps/transaction/nest-cli.json b/apps/transaction/nest-cli.json new file mode 100644 index 0000000000..f9aa683b1a --- /dev/null +++ b/apps/transaction/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/apps/transaction/package.json b/apps/transaction/package.json new file mode 100644 index 0000000000..689ef1c9f1 --- /dev/null +++ b/apps/transaction/package.json @@ -0,0 +1,76 @@ +{ + "name": "transaction", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "npx prisma migrate deploy && 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": "^11.0.1", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.1", + "@nestjs/microservices": "^11.1.9", + "@nestjs/platform-express": "^11.0.1", + "@prisma/client": "^5.22.0", + "kafkajs": "^2.2.4", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.10.7", + "@types/supertest": "^6.0.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^16.0.0", + "jest": "^30.0.0", + "prettier": "^3.4.2", + "prisma": "5.22.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} \ No newline at end of file diff --git a/apps/transaction/prisma/schema.prisma b/apps/transaction/prisma/schema.prisma new file mode 100644 index 0000000000..cfd1b59051 --- /dev/null +++ b/apps/transaction/prisma/schema.prisma @@ -0,0 +1,26 @@ +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "linux-musl", "linux-musl-openssl-3.0.x"] +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +model Transaction { + id String @id @default(uuid()) + accountExternalIdDebit String? + accountExternalIdCredit String? + transferTypeId Int + value Float + status TransactionStatus @default(PENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +enum TransactionStatus { + PENDING + APPROVED + REJECTED +} \ No newline at end of file diff --git a/apps/transaction/src/app.controller.spec.ts b/apps/transaction/src/app.controller.spec.ts new file mode 100644 index 0000000000..d22f3890a3 --- /dev/null +++ b/apps/transaction/src/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/apps/transaction/src/app.controller.ts b/apps/transaction/src/app.controller.ts new file mode 100644 index 0000000000..af856ba03b --- /dev/null +++ b/apps/transaction/src/app.controller.ts @@ -0,0 +1,40 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { EventPattern, Payload } from '@nestjs/microservices'; +import { AppService } from './app.service'; +import { CreateTransactionDto } from './dtos/create-transaction.dto'; + +@Controller('transactions') +export class AppController { + constructor(private readonly appService: AppService) {} + + @Post() + create(@Body() createTransactionDto: CreateTransactionDto) { + return this.appService.createTransaction(createTransactionDto); + } + + @EventPattern('transaction_status_update') + handleTransactionStatusUpdate(@Payload() data: any) { + this.appService.handleTransactionStatusUpdate(data); + } + + @Get(':id') + async getTransaction(@Param('id') id: string) { + const transaction = await this.appService.prisma.transaction.findUnique({ + where: { id }, + }); + + if (!transaction) return null; + + return { + transactionExternalId: transaction.id, + transactionType: { + name: transaction.transferTypeId, + }, + transactionStatus: { + name: transaction.status, + }, + value: transaction.value, + createdAt: transaction.createdAt, + }; + } +} diff --git a/apps/transaction/src/app.module.ts b/apps/transaction/src/app.module.ts new file mode 100644 index 0000000000..f7e0d010b7 --- /dev/null +++ b/apps/transaction/src/app.module.ts @@ -0,0 +1,33 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { PrismaService } from './prisma.service'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'KAFKA_SERVICE', + transport: Transport.KAFKA, + options: { + client: { + // <--- 2. USAR LA VARIABLE DE ENTORNO + brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'], + }, + consumer: { + groupId: 'transaction-consumer-client', + }, + }, + }, + ]), + ], + controllers: [AppController], + providers: [ + AppService, + PrismaService, + ], +}) +export class AppModule {} diff --git a/apps/transaction/src/app.service.ts b/apps/transaction/src/app.service.ts new file mode 100644 index 0000000000..44a9622442 --- /dev/null +++ b/apps/transaction/src/app.service.ts @@ -0,0 +1,62 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ClientKafka } from '@nestjs/microservices'; +import { PrismaService } from './prisma.service'; +import { CreateTransactionDto } from './dtos/create-transaction.dto'; +import { TransactionStatus } from '@prisma/client'; + +@Injectable() +export class AppService { + constructor( + public readonly prisma: PrismaService, + @Inject('KAFKA_SERVICE') private readonly kafkaClient: ClientKafka, + ) {} + + async createTransaction(data: CreateTransactionDto) { + const transaction = await this.prisma.transaction.create({ + data: { + accountExternalIdDebit: data.accountExternalIdDebit, + accountExternalIdCredit: data.accountExternalIdCredit, + transferTypeId: data.transferTypeId, + value: data.value, + status: 'PENDING', + }, + }); + + console.log( + `[Transaction] Creada en DB: ${transaction.id}. Enviando a Kafka...`, + ); + + this.kafkaClient.emit( + 'transaction_created', + JSON.stringify({ + transactionExternalId: transaction.id, + value: transaction.value, + }), + ); + + return transaction; + } + + async handleTransactionStatusUpdate(data: any) { + let transactionData = data; + + if (typeof data === 'string') { + transactionData = JSON.parse(data); + } + + console.log( + `[Transaction] Recibido update para ${transactionData.transactionExternalId}. Nuevo estado: ${transactionData.status}`, + ); + + await this.prisma.transaction.update({ + where: { + id: transactionData.transactionExternalId, + }, + data: { + status: transactionData.status as TransactionStatus, + }, + }); + + console.log(`[Transaction] DB actualizada correctamente.`); + } +} diff --git a/apps/transaction/src/dtos/create-transaction.dto.ts b/apps/transaction/src/dtos/create-transaction.dto.ts new file mode 100644 index 0000000000..1a035ef976 --- /dev/null +++ b/apps/transaction/src/dtos/create-transaction.dto.ts @@ -0,0 +1,6 @@ +export class CreateTransactionDto { + accountExternalIdDebit: string; + accountExternalIdCredit: string; + transferTypeId: number; + value: number; +} \ No newline at end of file diff --git a/apps/transaction/src/main.ts b/apps/transaction/src/main.ts new file mode 100644 index 0000000000..136d702566 --- /dev/null +++ b/apps/transaction/src/main.ts @@ -0,0 +1,24 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.connectMicroservice({ + transport: Transport.KAFKA, + options: { + client: { + brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'], + }, + consumer: { + groupId: 'transaction-consumer-server', + }, + }, + }); + + await app.startAllMicroservices(); + await app.listen(3000); + console.log('Transaction Service is running on port 3000 and listening to Kafka'); +} +bootstrap(); \ No newline at end of file diff --git a/apps/transaction/src/prisma.service.ts b/apps/transaction/src/prisma.service.ts new file mode 100644 index 0000000000..e6f150324d --- /dev/null +++ b/apps/transaction/src/prisma.service.ts @@ -0,0 +1,13 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } +} \ No newline at end of file diff --git a/apps/transaction/test/app.e2e-spec.ts b/apps/transaction/test/app.e2e-spec.ts new file mode 100644 index 0000000000..36852c54f0 --- /dev/null +++ b/apps/transaction/test/app.e2e-spec.ts @@ -0,0 +1,25 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import request from 'supertest'; +import { App } from 'supertest/types'; +import { AppModule } from './../src/app.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/apps/transaction/test/jest-e2e.json b/apps/transaction/test/jest-e2e.json new file mode 100644 index 0000000000..e9d912f3e3 --- /dev/null +++ b/apps/transaction/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/apps/transaction/tsconfig.build.json b/apps/transaction/tsconfig.build.json new file mode 100644 index 0000000000..64f86c6bd2 --- /dev/null +++ b/apps/transaction/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/apps/transaction/tsconfig.json b/apps/transaction/tsconfig.json new file mode 100644 index 0000000000..aba29b0e7f --- /dev/null +++ b/apps/transaction/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "resolvePackageJsonExports": true, + "esModuleInterop": true, + "isolatedModules": true, + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2023", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 0e8807f21c..268ad18853 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,25 +1,77 @@ -version: "3.7" services: - postgres: - image: postgres:14 - ports: - - "5432:5432" + mysql: + image: mysql:8.0 + container_name: challenge-mysql + restart: always environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres + MYSQL_ROOT_PASSWORD: "SuperSecureRootPassword!2025" + MYSQL_DATABASE: challenge_db + MYSQL_USER: admin_user + MYSQL_PASSWORD: "AppPassword_Strong#99" + ports: + - "3310:3306" + volumes: + - ./mysql-data:/var/lib/mysql + zookeeper: - image: confluentinc/cp-zookeeper:5.5.3 + image: confluentinc/cp-zookeeper:7.4.0 + container_name: challenge-zookeeper environment: ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + kafka: - image: confluentinc/cp-enterprise-kafka:5.5.3 - depends_on: [zookeeper] + image: confluentinc/cp-kafka:7.4.0 + container_name: challenge-kafka + depends_on: + - zookeeper + ports: + - "9092:9092" + - "29092:29092" environment: - KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_JMX_PORT: 9991 + + transaction-service: + build: + context: ./apps/transaction + dockerfile: Dockerfile + container_name: app-transaction + restart: always ports: - - 9092:9092 + - "3000:3000" + depends_on: + - mysql + - kafka + environment: + DATABASE_URL: "mysql://root:SuperSecureRootPassword!2025@mysql:3306/challenge_db" + + KAFKA_BROKERS: "kafka:9092" + KAFKA_GROUP_ID: "transaction-consumer-server" + + anti-fraud-service: + build: + context: ./apps/anti-fraud + dockerfile: Dockerfile + container_name: app-anti-fraud + restart: always + depends_on: + - kafka + environment: + KAFKA_BROKERS: "kafka:9092" + KAFKA_GROUP_ID: "anti-fraud-group" + + kafka-ui: + image: provectuslabs/kafka-ui:latest + container_name: challenge-kafka-ui + depends_on: + - kafka + ports: + - "8080:8080" + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000..a212ba0377 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "prisma": "^7.1.0" + }, + "dependencies": { + "@nestjs/config": "^4.0.2", + "@nestjs/microservices": "^11.1.9", + "kafkajs": "^2.2.4" + } +}