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
33 changes: 33 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Database
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/yape_transaction_db"

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0

# Cache Configuration
CACHE_DEFAULT_TTL=300
CACHE_CATALOG_TTL=86400
CACHE_TRANSACTION_TTL=900
CACHE_CONFIG_TTL=300

# Kafka Configuration
KAFKA_BROKERS=localhost:9092
KAFKA_CLIENT_ID=yape-app
KAFKA_GROUP_ID=transaction-consumer

# Fraud Detection
FRAUD_REJECTION_THRESHOLD=1000
FRAUD_EVALUATION_TIMEOUT=5000
MAX_DAILY_TRANSACTION_VALUE=10000
MAX_TRANSACTION_VALUE=5000

# Application Ports
TRANSACTION_SERVICE_PORT=3001
ANTI_FRAUD_SERVICE_PORT=3000

# Environment
NODE_ENV=development
LOG_LEVEL=debug
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
17 changes: 17 additions & 0 deletions apps/anti-fraud/src/anti-fraud.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AntiFraudController } from './anti-fraud.controller';
import { AntiFraudService } from './anti-fraud.service';

describe('AntiFraudController', () => {
let antiFraudController: AntiFraudController;

beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AntiFraudController],
providers: [AntiFraudService],
}).compile();

antiFraudController = app.get<AntiFraudController>(AntiFraudController);
});

});
44 changes: 44 additions & 0 deletions apps/anti-fraud/src/anti-fraud.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { AntiFraudService } from './anti-fraud.service';
import { EvaluateTransactionDto } from './dto/evaluate-transaction.dto';

@Controller('anti-fraud')
export class AntiFraudController {
constructor(private readonly antiFraudService: AntiFraudService) { }

@MessagePattern('transaction_created')
handleTransactionCreated(@Payload() data: any): string {
console.log('Received transaction_created event:', data);

try {
const transactionData = this.parseTransactionData(data);
const evaluationResult = this.antiFraudService.evaluate(transactionData);
const response = this.buildResponse(transactionData, evaluationResult);
return JSON.stringify(response);
} catch (error) {
console.error('Error processing transaction:', error);
throw error;
}
}

private parseTransactionData(data: any): EvaluateTransactionDto {
if (!data || !data.transactionId || data.value === undefined) {
throw new Error('Invalid transaction data received');
}

return {
transactionId: data.transactionId,
value: data.value,
};
}


private buildResponse(dto: EvaluateTransactionDto, status: string): EvaluateTransactionDto {
return {
...dto,
status,
};
}

}
10 changes: 10 additions & 0 deletions apps/anti-fraud/src/anti-fraud.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { AntiFraudController } from './anti-fraud.controller';
import { AntiFraudService } from './anti-fraud.service';

@Module({
imports: [],
controllers: [AntiFraudController],
providers: [AntiFraudService],
})
export class AntiFraudModule { }
15 changes: 15 additions & 0 deletions apps/anti-fraud/src/anti-fraud.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { fraudConfig } from '../../shared/config/fraud.config';
import { EvaluateTransactionDto } from './dto/evaluate-transaction.dto';

@Injectable()
export class AntiFraudService {

evaluate(dto: EvaluateTransactionDto): string {
if (dto.value > fraudConfig.evaluation.rejectionThreshold) {
return 'rejected';
}

return 'approved';
}
}
15 changes: 15 additions & 0 deletions apps/anti-fraud/src/dto/evaluate-transaction.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID } from "class-validator";

export class EvaluateTransactionDto {
@IsUUID()
@IsNotEmpty()
transactionId: string;

@IsNumber()
@IsNotEmpty()
value: number;

@IsOptional()
@IsString()
status?: string;
}
23 changes: 23 additions & 0 deletions apps/anti-fraud/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AntiFraudModule } from './anti-fraud.module';

async function bootstrap() {
const app = await NestFactory.create(AntiFraudModule);

app.connectMicroservice<MicroserviceOptions>({
transport: Transport.KAFKA,
options: {
client: {
brokers: ['localhost:9092'],
},
consumer: {
groupId: 'transaction-consumer',
},
},
});

await app.startAllMicroservices();
await app.listen(process.env.port ?? 3000);
}
bootstrap();
24 changes: 24 additions & 0 deletions apps/anti-fraud/test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AntiFraudModule } from './../src/anti-fraud.module';

describe('AntiFraudController (e2e)', () => {
let app: INestApplication;

beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AntiFraudModule],
}).compile();

app = moduleFixture.createNestApplication();
await app.init();
});

it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
9 changes: 9 additions & 0 deletions apps/anti-fraud/test/jest-e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
9 changes: 9 additions & 0 deletions apps/anti-fraud/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": false,
"outDir": "../../dist/apps/anti-fraud"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
52 changes: 52 additions & 0 deletions apps/shared/cache/cache-admin.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Body, Controller, Delete, Get, Post } from '@nestjs/common';
import { CacheAdminService } from '../../shared/cache/cache-admin.service';


@Controller('admin/cache')
export class CacheAdminController {
constructor(private readonly cacheAdminService: CacheAdminService) { }

@Get('stats')
async getCacheStats() {
return this.cacheAdminService.getCacheStats();
}

@Get('health')
async checkHealth() {
return this.cacheAdminService.checkCacheHealth();
}

@Get('dashboard')
async getDashboard() {
return this.cacheAdminService.getDashboardData();
}

@Delete('catalogs')
async clearCatalogs() {
return this.cacheAdminService.clearCatalogCache();
}


@Delete('all')
async clearAllCache() {
return this.cacheAdminService.clearAllCache();
}

@Post('maintenance')
async performMaintenance(
@Body() options: {
clearCatalogs?: boolean;
clearTransactions?: boolean;
warmUp?: boolean;
} = {}
) {
return this.cacheAdminService.performMaintenance(options);
}

@Post('troubleshoot')
async troubleshoot(
@Body() { scope }: { scope?: 'all' | 'catalogs' | 'transactions' } = { scope: 'all' }
) {
return this.cacheAdminService.troubleshootInconsistentData(scope);
}
}
Loading