Skip to content

Marcos Huaranga - SOLUTION CHALLENGE #488

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

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5a7776b
chore: add turbo repo using pnpm
dimacros Apr 22, 2025
3bd5707
chore: add api, antifraud and transaction repos for monorepo
dimacros Apr 22, 2025
fdd435f
chore: refactor files (packge.json, main)
dimacros Apr 22, 2025
2988122
chore: update deps
dimacros Apr 22, 2025
645ffe6
Refactor code structure for improved readability and maintainability
dimacros Apr 23, 2025
932bc0a
feat: Refactor code structure for improved readability and maintainab…
dimacros Apr 25, 2025
5050fec
chore: rename microservices to modules
dimacros Apr 25, 2025
6f2852c
feat(transaction): enhance module definition and service integration
dimacros Apr 25, 2025
a2566f8
feat(fraud): add FraudController and update module integration
dimacros Apr 25, 2025
8fcf3c4
feat(fraud): initialize fraud module with service and configuration f…
dimacros Apr 25, 2025
5817515
feat(common): add initial module structure with configuration files
dimacros Apr 25, 2025
607e237
refactor(transaction): remove transaction.module-definition and integ…
dimacros Apr 25, 2025
5df4604
refactor(db): eliminar archivos y configuraciones obsoletas del módul…
dimacros Apr 25, 2025
93940b7
feat: implement QueryPublisherAdapter for Kafka communication
dimacros Apr 25, 2025
65c78cc
feat(turbo): agregar dependencia de construcción en la tarea de desar…
dimacros Apr 25, 2025
f0f390a
feat(fraud-proxy): implementar adaptadores de publicación y configura…
dimacros Apr 25, 2025
b0cb99f
add
dimacros Apr 25, 2025
1457bbc
docs: agregar archivo CHALLENGE.md y actualizar enlace en README.md
dimacros Apr 25, 2025
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
34 changes: 34 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose.y*ml
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/build
**/dist
LICENSE
README.md
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

.turbo
106 changes: 106 additions & 0 deletions CHALLENGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Yape Code Challenge 🚀

Welcome to the **Yape Code Challenge**! This project is designed to showcase your skills in building scalable, maintainable, and efficient backend systems. The challenge involves implementing a robust microservices architecture using modern tools and frameworks, with a focus on clean code, modular design, and seamless communication between services.

## Key Features

- **Microservices Architecture**: Leverage NestJS to build modular and scalable services.
- **Event-Driven Communication**: Use Kafka for asynchronous messaging between services.
- **Database Integration**: Design and implement a relational database schema using Prisma.
- **Clean Code Practices**: Follow industry standards for maintainable and testable code.
- **Task Automation**: Utilize TurboRepo for efficient task management and dependency handling.

## Objectives

1. Implement a **Gateway** service to handle API requests and route commands/queries to the appropriate processors.
2. Build a **Processor** service to handle business logic, process commands, and respond to queries.
3. Ensure seamless communication between services using Kafka as the messaging backbone.
4. Design a relational database schema to store and manage transactional data.
5. Write unit and integration tests to ensure the reliability of the system.

## Technologies Used

- **NestJS**: A progressive Node.js framework for building efficient and scalable server-side applications.
- **Kafka**: A distributed event streaming platform for asynchronous communication.
- **Prisma**: A modern ORM for database management and schema design.
- **TurboRepo**: A high-performance build system for managing monorepos.
- **TypeScript**: A strongly typed programming language for building robust applications.

## Getting Started

Getting started is simple and hassle-free! Follow these steps to spin up the entire system with a single command:

1. **Clone the Repository**:
```bash
git clone https://github.com/your-repo/yape-code-challenge.git
cd yape-code-challenge

This challenge is an excellent opportunity to demonstrate your expertise in backend development, microservices, and event-driven architectures. Good luck, and happy coding! 🚀

2. **Run the Services with Docker Compose**: Ensure you have Docker and Docker Compose installed, then run:
```bash
docker-compose up --build

AND

```bash
docker compose up -d
pnpm install & pnpm dev

That's it! With a single command, your environment is ready to go. Dive into the code, explore the architecture, and start building something amazing.

## Try the endpoints

### 1. Create a Transaction

Endpoint: `POST /v1/transactions`

Description: Creates a new transaction.

Request Body:
```json
{
"transactionExternalId": "string",
"accountExternalIdDebit": "string",
"accountExternalIdCredit": "string",
"transactionType": {
"id": "string"
},
"value": 100.50
}'
```

#### cURL Command:

```bash
curl -X POST http://localhost:3000/v1/transactions \
-H "Content-Type: application/json" \
-d '{
"transactionExternalId": "12345",
"accountExternalIdDebit": "debit-account-id",
"accountExternalIdCredit": "credit-account-id",
"transactionType": {
"id": "1"
},
"value": 100.50
}'
```

### 2. Get All Transactions

Description: Retrieves all transactions based on query parameters.
```bash
curl -X GET "http://localhost:3000/v1/transactions?page=1&limit=10"
```

### 3. Get Transaction by ID

Endpoint: `GET /v1/transactions/:transactionExternalI`

**Description** Retrieves a transaction by its external ID.

**cURL Command:**

```bash
curl -X GET http://localhost:3000/v1/transactions/12345
```
35 changes: 35 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7

ARG NODE_VERSION=22.12.0
ARG PNPM_VERSION=10.9.0

FROM node:${NODE_VERSION}

# Use production node environment by default.
ENV NODE_ENV=development

# Install pnpm.
RUN --mount=type=cache,target=/root/.npm \
npm install -g pnpm@${PNPM_VERSION}

WORKDIR /usr/src/app

# Copy the rest of the source files into the image.
COPY . .

RUN pnpm install --recursive
RUN pnpm build --filter=@yape-modules/core
RUN pnpm build
# RUN npx ts-node modules/core/prisma/seed.ts
RUN cd modules/core && pnpm db:seed

# Expose the port that the application listens on.
EXPOSE 3000

CMD ["sh", "-c", "pnpm start --filter @yape/processor && pnpm start --filter @yape/gateway"]
22 changes: 22 additions & 0 deletions README.Docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
### Building and running your application

When you're ready, start your application by running:
`docker compose up --build`.

Your application will be available at http://localhost:3000.

### Deploying your application to the cloud

First, build your image, e.g.: `docker build -t myapp .`.
If your cloud uses a different CPU architecture than your development
machine (e.g., you are on a Mac M1 and your cloud provider is amd64),
you'll want to build the image for that platform, e.g.:
`docker build --platform=linux/amd64 -t myapp .`.

Then, push it to your registry, e.g. `docker push myregistry.com/myapp`.

Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/)
docs for more detail on building and pushing.

### References
* [Docker's Node.js guide](https://docs.docker.com/language/nodejs/)
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Don't forget that the proper way to submit your work is to fork the repo and cre
- [Tech Stack](#tech_stack)
- [Send us your challenge](#send_us_your_challenge)

# Here the solution
For more details, refer to the [CHALLENGE.md](./CHALLENGE.md) file.

# Problem

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.
Expand Down
4 changes: 4 additions & 0 deletions apps/gateway/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
34 changes: 34 additions & 0 deletions apps/gateway/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @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'
},
},
);
8 changes: 8 additions & 0 deletions apps/gateway/nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
79 changes: 79 additions & 0 deletions apps/gateway/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"name": "@yape/gateway",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest 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.0",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.0",
"@nestjs/cqrs": "^11.0.0",
"@nestjs/microservices": "^11.1.0",
"@nestjs/platform-express": "^11.1.0",
"@yape-modules/core": "workspace:*",
"@yape-modules/transaction": "workspace:*",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.25.1",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.0",
"@swc/cli": "^0.6.0",
"@swc/core": "^1.11.22",
"@types/jest": "^29.5.14",
"@types/node": "^22.14.1",
"@types/supertest": "^6.0.3",
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"jest": "^29.7.0",
"prettier": "^3.5.3",
"source-map-support": "^0.5.21",
"supertest": "^7.1.0",
"ts-jest": "^29.3.2",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.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"
}
}
Loading